MMCV学习——基础篇1(配置&注册机制)

 博主最近在学习mmcv这个基础库,所以写一个博客记录一下。一来是作为学习笔记,方便日后查阅,二来是和大家分享交流一下我得学习心得。mmcv是由OpenMMLab开源的一个计算机视觉的基础库,支持了OpenMMLab众多开源的优秀仓库(比如,MMDetectionMMSegmentationMMSelfSup等等)。合抱之木,生于毫末。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)
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐