MMCV学习——基础篇1(配置&注册机制)
MMCV学习 博主最近在学习mmcv这个基础库,所以写一个博客记录一下。一来是作为学习笔记,方便日后查阅,二来是和大家分享交流一下我得学习心得。mmcv是由OpenMMLab开源的一个计算机视觉的基础库,支持了OpenMMLab众多开源的优秀仓库(比如,MMDetection、MMSegmentation、MMSelfSup等等)。合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。mmc
MMCV学习——基础篇1(配置&注册机制)
博主最近在学习
mmcv
这个基础库,所以写一个博客记录一下。一来是作为学习笔记,方便日后查阅,二来是和大家分享交流一下我得学习心得。mmcv
是由OpenMMLab开源的一个计算机视觉的基础库,支持了OpenMMLab众多开源的优秀仓库(比如,MMDetection、MMSegmentation、MMSelfSup等等)。合抱之木,生于毫末。mmcv在OpenMMLab构建的整个生态体系之中有着举足轻重的地位,下面就请大家和博主一起来学习这个基础库吧。
PS: 目前博主先按照自己的学习顺序来组织博客的结构(就是MMCV官方Doc的顺序),若后续内容较多再考虑是否按照专栏的形式重构。
文章目录
1. Introduction
mmcv作为一个基础库,主要提供了以下的功能模块:
- 统一可扩展的 io api
- 支持非常丰富的图像/视频处理算子
- 图片/视频的标注文件可视化
- 常用的工具类例如 timer 和 progress bar 等等
- 上层框架需要的 hook 机制以及可以直接使用的 runner
- 高度灵活的 cfg 模式和注册器机制
- 高效高质量的 cuda op
2. Installation
主要有两个版本的mmcv:
- mmcv-full:提前编译了cuda op,提供了mmcv全部特性。(推荐安装)
- mmcv-lite:没有cuda op,其他特性完全保留。
其pip安装命令格式如下:
pip install mmcv-full=={mmcv_version} -f https://download.openmmlab.com/mmcv/dist/{cu_version}/{torch_version}/index.html
以torch1.6+cuda10.2的mmcv-full安装命令为例:
# 不写版本默认就是最新版本
pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.6.0/index.html
3. 使用MMCV源码构建
TODO 这一部分暂时跳过,有需要的读者可以直接去官网查看文档 BUILD MMCV FROM SOURCE。
4. MMCV核心组件——Config & Registry
4.1 Config类
先来看看它的官网解释:
Config
class is used for manipulating config and config files. It supports loading configs from multiple file formats including python, json and yaml. It provides dict-like apis to get and set values.
4.1.1 从配置文件中生成Config类
假设我们这里有一个config.py文件定义如下:
a = 1
b = dict(b1=[0, 1, 2], b2=None)
c = (1, 2)
d = 'string'
我们直接读取这个python文件,然后打印出来:
config = Config.fromfile("config.py")
print(config)
print(type(config))
# Output
# Config (path: config.py): {'a': 1, 'b': {'b1': [0, 1, 2], 'b2': None}, 'c': (1, 2), 'd': 'string'}
# <class 'mmcv.utils.config.Config'>
4.1.2 预定义变量解析
除了直接定义配置变量之外,Config还支持解析预定义变量,其格式如下 { { v a r } } \{\{ var \}\} {{var}}。目前,Config支持解析四种预定义的变量,可以方便地获取被打开的配置文件所在绝对路径的各个部分:
- { { f i l e D i r n a m e } } \{\{ \mathrm{fileDirname} \}\} {{fileDirname}} - 当前被打开的配置文件所在目录。
- { { f i l e B a s e n a m e } } \{\{ \mathrm{fileBasename} \}\} {{fileBasename}} - 当前被打开的配置文件名字,附带后缀。
- { { f i l e B a s e n a m e N o E x t e n s i o n } } \{\{ \mathrm{fileBasenameNoExtension} \}\} {{fileBasenameNoExtension}} - 当前被打开的配置文件名字,不带后缀。
- { { f i l e E x t n a m e } } \{\{ \mathrm{fileExtname } \}\} {{fileExtname}} - 当前被打开的配置文件名后缀。
不过,其实从之前打印Config的示例对象时可以看出来现在的mmcv给Config示例提供了对象属性path,我们可以直接使用path这个属性获取被打开配置的绝对路径。
4.1.3 配置文件的继承
mmcv考虑到了提供配置文件的可复用性,提供了一系列的配置文件继承的操作,并让用户可以灵活选定被继承的部分。
想要让一个配置文件继承另一个配置文件,只需要在子配置文件之中指定_base_变量为父配置文件的 相对路径(相对于config_b) 即可。例如,我们有一个config_a.py配置文件如下:
# config_a.py
a = 1
b = dict(b1=[0, 1, 2], b2=None)
为了继承config_a,我们可以在config_b文件中指定_base_变量:
# config_b.py
_base_ = './config_a.py'
b = dict(b2=1)
c = (1, 2)
现在我们输出由config_b.py解析出来的对象:
config = Config.fromfile("./config_b.py")
print(config)
# Output
# Config (path: ./config_b.py): {'a': 1, 'b': {'b1': [0, 1, 2], 'b2': 1}, 'c': (1, 2)}
从上面的输出可以看出来,当子配置文件和父配置文件存在key冲突的时候(b.b2),会以子配置文件为主。这一行为和面向对象之中的继承过程十分相似,所以这里也被叫做配置文件的继承而不是配置文件的合成。PS: Config支持从多个文件之中继承,只需要把_base_定义为[‘config1.py’, ‘config2.py’, …]这样一个列表即可,但是base配置之中不能存在相同的key,否则Config就不知道该以哪个base为准了。
下面我们来看一个Config继承之中比较有用的语法,可以让我们直接在子配置文件中使用父配置文件中定义的变量。例如,我们要在子配置文件config_c.py中使用父配置文件config_a.py中的变量:
# config_c.py
_base_ = ['./config_a.py']
item = dict(a = {{ _base_.a}}, b = {{_base_.b.b1}})
config = Config.fromfile("./config_c.py")
print(config)
# Output:
# Config (path: ./config_c.py): {'a': 1, 'b': {'b1': [0, 1, 2], 'b2': None}, 'item': {'a': 1, 'b': [0, 1, 2]}}
4.1.4 扩展阅读
参见OpenMMLab在知乎上也发布的MMCV Config中文详解。
4.2 Registry
官方解释:
MMCV implements
registry
to manage different modules that share similar functionalities, e.g., backbones, head, and necks, in detectors.
在MMCV之中可以将registry看做是字符串到class之间的映射,这些同一个registry之下的class有着相似的功能,有点类似于面向对象的多态。有了registry之后,用户可以之间使用字符串调用和实例化那些class。Registry的使用流程主要分为一下三步骤:
- 创建构造方法(optional)
- 创建registry
- 使用了registry去管理这些modules
4.2.1 入门示例
下面来一个简单的例子,看看怎么用Registry去管理一个包下面的的module。假设我们有一个converter的包,理由有我们实现的不同功能的converter类。
首先,我们要现在创建converter/builder.py文件去为converter创建registry:
# converter/builder.py
from mmcv.utils import Registry
CONVERTERS = Registry("converter")
然后通过CONVERTERS去注册我们要管理的Converter1这个类:
# converter/conerter1.py
from .builder import CONVERTERS
# 这里需要说明一下, 由于python的装饰器机制是在类定义被加载时触发,所以一般需要在converter/__init__.py里面去import这个类。
@CONVERTERS.register_module()
class Converter1(object):
def __init__(self, a, b):
self.a = a
self.b = b
假设在外部有主文件要是用Converter1这个类,那么我们的写法就可以变得更加优雅而不是到处去import:
# main.py
from converter import CONVERTERS
if __name__ == "__main__":
converter_cfg = dict(type='Converter1', a=1, b=2)
converter = CONVERTERS.build(converter_cfg) # 这里的build是mmcv帮我们默认实现了
print(converter)
4.2.2 自定义构建函数(Build Function)
上面那个入门例子只是讲解了如何去利用Registry去实例化被管理的类,默认情况下使用mmcv默认实现的build函数读取配置文件并实例化已经足够了。这样的管理方式实现了直接由配置文件到实例化类的过程,这样以后要修改一些参数可以只用改配置文件就行了,十分方便。
如果我们需要自定义构建函数,那么可以按照以下的方法自定义:
# converter/builder.py
from mmcv.utils import Registry
def build_converter(cfg, registry, *args, **kwargs):
# print(args)
# print(kwargs)
cfg_ = cfg.copy()
converter_type = cfg_.pop("type")
if converter_type not in registry:
raise KeyError(f"Unrecognized converter type {converter_type}")
else:
converter_cls = registry.get(converter_type)
converter = converter_cls(*args, **kwargs, **cfg_)
return converter
CONVERTERS = Registry("converter", build_func=build_converter)
4.2.3 层次注册
MMCV下游的所有代码库model的registry都是mmcv的MODELS
的子注册,这里一般有两种方式从姊妹的registry和父类的registry中实例化model:
(1)从姊妹的registry实例化:
# 假设我们有一个classfication的代码库叫mmcls.py
from mmcv.utils import Registry
from mmcv.cnn import MODELS as MMCV_MODELS
from torch import nn
# Create a register
MODELS = Registry("model", parent=MMCV_MODELS)
# classification net
@MODELS.register_module()
class ClsNet(nn.Module):
def __init__(self) -> None:
super().__init__()
# detection的代码库叫mmdet.py
from mmcv.utils import Registry
from mmcv.cnn import MODELS as MMCV_MODELS
import mmcls
from torch import nn
# Create a register
MODELS = Registry("model", parent=MMCV_MODELS)
# Detection net
@MODELS.register_module()
class DetNet(nn.Module):
def __init__(self) -> None:
super().__init__()
# 这里我们使用mmdet的registry去实例化cls_net和det_net, 所以det_net不加模块名而cls_net要加
if __name__ == "__main__":
det_net = MODELS.build({"type": "DetNet"})
cls_net = MODELS.build({"type": "mmcls.ClsNet"})
print(det_net)
print(cls_net)
(2)从父类的registry实例化:
# 外部父类的文件hierarchy_reg.py实例化
from mmcv.cnn import MODELS as MMCV_MODELS
import mmcls
import mmdet
if __name__ == "__main__":
det_net = MMCV_MODELS.build({"type": "mmdet.DetNet"})
cls_net = MMCV_MODELS.build({"type": "mmcls.ClsNet"})
print(det_net)
print(cls_net)
更多推荐
所有评论(0)