准备资料
本系列文章主要介绍mmcv及mmdetection源码解读。因此,建议读者首先在本地装好mmdetection环境。安装教程:mmdet2.8最新版安装教程!
前言
本篇是mmcv源码解读的Config类介绍。代码地址在mmcv/utils/config.py文件中。
1、FasterRcnn为例
网上大多数Config类讲解是特别干的代码介绍,缺乏一个具像的例子来深刻理解。因此,本文以mmdetection中的FasterRcnn网络为例来介绍Config类。(当然,Config类还有好多功能,还需学习探索,欢迎交流:Q2541612007)。
贴下FasterRcnn的配置文件mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py):
_base_ = [
'../_base_/models/faster_rcnn_r50_fpn.py',
'../_base_/datasets/coco_detection.py',
'../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'
]
上述配置文件中,_base_是个长度为4的list:模型,数据集,优化器,训练方式。这里说下Config类作用:“将配置文件中的字段转成字典的形式”。举个简单例子:以coco_detection.py部分文件字段为例:
dataset_type = 'CocoDataset'
data_root = 'data/coco/'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
感性上来说:就是将 上述 “=”左边的视为字典的key;“=”右边的内容视为value。我把其成为“等号规则”。最终经过Config类返回的就是一个实例化的cfg对象,用_cfg_dict初始化Config类,变成一个属性。实际上_cfg_dict依旧是一个字典。
2、实现流程
从第一部分可以看出:Config类实际上完成两部分工作:
(1)将配置文件按照“等号规则“将配置文件的内容放入一个字典_cfg_dict。
(2)用字典_cfg_dict完成Config类的初始化,使其成为Config类的一个属性。
因此,对于第一部分工作实际上属于Config类的一个公用方法。因此,可以用Python中的静态方法 @staticmethod完成。第二部分则直接init类即可。接下来我会用两部分分别介绍。流程见下图:
2.1. 配置文件转入字典(_file2dict函数)
从faster_rcnn的配置文件中看出:其实一个以 _base_字段开头的列表。而列表中元素是四个路径。因此,函数程序需要遍历4个路径,然后添加进一个字典中。函数整体逻辑如下(用ppt画的,见谅~):
整体逻辑厘清之后,看代码就容易多了。我这里仅贴部分核心的(很多细节我也看不懂,大佬写的太牛了)。
@staticmethod
def _file2dict(filename, use_predefined_variables=True):
filename = osp.abspath(osp.expanduser(filename)) # filename: py文件的绝对路径
# 将file转入一个字典
cfg_dict = {
name: value
for name, value in mod.__dict__.items()
if not name.startswith('__')
}
if BASE_KEY in cfg_dict: # 判断是否有_base_字段
base_filename = cfg_dict.pop(BASE_KEY) # base_filename = ['../_base_/models/faster_rcnn_r50_fpn.py','../_base_/datasets/coco_detection.py','','']
cfg_dict_list = list() # 存储最终结果
cfg_text_list = list()
for f in base_filename:
_cfg_dict, _cfg_text = Config._file2dict(osp.join(cfg_dir, f)) # 遍历递归调用file2path
cfg_dict_list.append(_cfg_dict)
cfg_text_list.append(_cfg_text)
# 遍历结束后,cfg_dict_list=[{},{},{},{}] , cfg_text_list = ['','','',''] 里面存储的是 四个配置文件中内容
base_cfg_dict = dict()
for c in cfg_dict_list:
if len(base_cfg_dict.keys() & c.keys()) > 0:
raise KeyError('Duplicate key is not allowed among bases')
base_cfg_dict.update(c)
return cfg_dict, cfg_text
大致应该没问题。比较容易理解。
2.2. Config类初始化
2.1得到了cfg_dict,然后就可以用其完成初始化了。
def __init__(self, cfg_dict=None, cfg_text=None, filename=None):
super(Config, self).__setattr__('_cfg_dict', ConfigDict(cfg_dict))# Config类设置一个_cfg_dict属性,并将cfg_dict实例为ConfigDict类。
super(Config, self).__setattr__('_filename', filename) # Config类设置一个_filename属性。
super(Config, self).__setattr__('_text', text) # Config在添加一个'_text'属性
def __setattr__(self, name, value):
if isinstance(value, dict):
value = ConfigDict(value)
self._cfg_dict.__setattr__(name, value)
代码中重写了__setattr__方法。简单就是将cfg_dict变成了一个ConfigDict类。实质还是一个字典,只不过重写了__getattr__方法,在根据key索引时加入了一些逻辑判断(keyerror啥的),这里将其依旧理解成一个普通字典就行。
class ConfigDict(Dict):
def __missing__(self, name):
raise KeyError(name)
def __getattr__(self, name):
try:
value = super(ConfigDict, self).__getattr__(name)
except KeyError:
ex = AttributeError(f"'{self.__class__.__name__}' object has no "
f"attribute '{name}'")
except Exception as e:
ex = e
else:
return value
raise ex
总结
本篇主要介绍的是Config类的设计理念把。实际上Config类还有好多方法,比如_merge_a_to_b等。不过都逃不过那两部分:根据等号规则将字段变成dict,然后利用dict初始化Config类。
如有问题, 欢迎提问交流Q2541612007。