MMCV核心组件Config
配置文件读取
配置类提供了统一的接口 Config.fromfile()
,来读取和解析配置文件。
合法的配置文件应该定义一系列键值对,这里举几个不同格式配置文件的例子。
Python 格式:
1 2 3
| test_int = 1 test_list = [1, 2, 3] test_dict = dict(key1='value1', key2=0.1)
|
Json 格式:
1 2 3 4 5
| { "test_int": 1, "test_list": [1, 2, 3], "test_dict": {"key1": "value1", "key2": 0.1} }
|
YAML 格式:
1 2 3 4 5
| test_int: 1 test_list: [1, 2, 3] test_dict: key1: "value1" key2: 0.1
|
对于以上三种格式的文件,假设文件名分别为 config.py
,config.json
,config.yml
,调用 Config.fromfile('config.xxx')
接口加载这三个文件都会得到相同的结果
1 2 3 4 5 6
| from mmengine.config import Config
cfg = Config.fromfile('learn_read_config.py') print(cfg) >>> Config (path: learn_read_config.py): {'test_int': 1, 'test_list': [1, 2, 3], 'test_dict': {'key1': 'value1', 'key2': 0.1}}
|
配置文件的使用
ConfigDict
是 MMCV 库中用于配置文件管理的一个类。它主要用于存储模型训练、测试等过程中的各种参数设置。ConfigDict
类在功能上类似于 Python 的内置字典类型 dict
,但是它提供了属性的访问方式,以方便用户在配置文件中进行参数的定义、修改和访问
通过读取配置文件来初始化配置对象后,就可以像使用普通字典或者 Python 类一样来使用这个变量了。这里提供了字典访问和属性访问两种接口, cfg['key']
或者 cfg.key
。这两种接口都支持读写
ConfigDict
允许使用点来访问键值,就像访问对象的属性一样。例如可以通过 cfg.model
来访问
- 而普通的
dict
只能通过方括号 []
来访问键值,如 cfg['model']
关于为什么能用属性的方法进行成员的访问,进入源码之后我们可以看到原因是使用了第三方库addict
实现,这里就不展开看源码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| print(cfg.test_int) print(cfg.test_list) print(cfg.test_dict) cfg.test_int = 2
print(cfg['test_int']) print(cfg['test_list']) print(cfg['test_dict']) cfg['test_list'][1] = 3 print(cfg['test_list']) >>> 1 [1, 2, 3] {'key1': 'value1', 'key2': 0.1} 2 [1, 2, 3] {'key1': 'value1', 'key2': 0.1} [1, 3, 3]
|
在算法库中,可以将配置与注册器结合起来使用,达到通过配置文件来控制模块构造的目的。例如我们已经定义了一个优化器的注册器 OPTIMIZERS,包括了各种优化器。那么首先写一个 config_sgd.py
:
1
| optimizer = dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001)
|
然后在算法库中可以通过如下代码构造优化器对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from mmengine import Config, optim from mmengine.registry import OPTIMIZERS
import torch.nn as nn
cfg = Config.fromfile('config_sgd.py')
model = nn.Conv2d(1, 1, 1) cfg.optimizer.params = model.parameters() optimizer = OPTIMIZERS.build(cfg.optimizer) print(optimizer) >>> SGD ( Parameter Group 0 dampening: 0 foreach: None lr: 0.1 maximize: False momentum: 0.9 nesterov: False weight_decay: 0.0001 )
|
配置文件的继承
有时候两个不同的配置文件之间的差异很小,可能仅仅只改了一个字段,我们就需要将所有内容复制粘贴一次,而且在后续观察的时候,不容易定位到具体差异的字段
继承机制概述
这里我们举一个例子来说明继承机制。定义如下两个配置文件,
optimizer_cfg.py
:
1
| optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
|
resnet50.py
:
1 2
| _base_ = ['optimizer_cfg.py'] model = dict(type='ResNet', depth=50)
|
虽然我们在 resnet50.py
中没有定义 optimizer 字段,但由于我们写了 _base_ = ['optimizer_cfg.py']
,会使这个配置文件获得 optimizer_cfg.py
中的所有字段。
1 2 3 4
| cfg = Config.fromfile('resnet50.py') print(cfg.optimizer) >>> {'type': 'SGD', 'lr': 0.02, 'momentum': 0.9, 'weight_decay': 0.0001}
|
_base_
是配置文件的保留字段,指定了该配置文件的继承来源。支持继承多个文件,将同时获得这多个文件中的所有字段
runtime_cfg.py
:
resnet50_runtime.py
:
1 2
| _base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] model = dict(type='ResNet', depth=50)
|
这时,读取配置文件 resnet50_runtime.py
会获得 3 个字段 model
,optimizer
,gpu_ids
。
1 2 3 4
| cfg = Config.fromfile('resnet50_runtime.py') print(cfg.optimizer) >>> {'optimizer': {'type': 'SGD', 'lr': 0.02, 'momentum': 0.9, 'weight_decay': 0.0001}, 'gpu_ids': [0, 1], 'model': {'type': 'ResNet', 'depth': 50}}
|
修改继承字段(在引用配置文件的地方修改配置)
有时候,我们继承一个配置文件之后,可能需要对其中个别字段进行修改,有两种修改方式,继承式修改和覆盖式修改:
继承式修改和覆盖式修改的区别在于dict
函数第一个参数是否有_delete_=True
,若没有则为继承式修改,只会修改指定的配置,反之则删光之前的所有配置,开始一个新的配置
例如需要改变_base_
文件中optimizer的配置:
resnet50_lr0.01.py
:
1 2 3 4 5 6
| _base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] model = dict(type='ResNet', depth=50)
optimizer = dict(lr=0.01)
optimizer = dict(_delete_=True, type='SGD', lr=0.01)
|
引用被继承文件中的变量(在配置文件内部修改配置)
有时我们想重复利用 _base_
中定义的字段内容,就可以通过 {{_base_.xxxx}}
获取来获取对应变量的拷贝。例如:
1 2 3
| refer_base_var.py _base_ = ['resnet50.py'] a = {{_base_.model}}
|
解析后发现,a
的值变成了 resnet50.py
中定义的 model
1 2 3 4
| cfg = Config.fromfile('refer_base_var.py') print(cfg.a) >>> {'type': 'ResNet', 'depth': 50}
|
我们可以在 json
、yaml
、python
三种类型的配置文件中,使用这种方式来获取 _base_
中定义的变量
尽管这种获取 _base_
中定义变量的方式非常通用,但是在语法上存在一些限制,无法充分利用 python
类配置文件的动态特性。比如我们想在 python
类配置文件中,修改 _base_
中定义的变量:
1 2 3
| _base_ = ['resnet50.py'] a = {{_base_.model}} a['type'] = 'MobileNet'
|
配置类是无法解析这样的配置文件的(解析时报错)。配置类提供了一种更 pythonic
的方式,让我们能够在 python
类配置文件中修改 _base_
中定义的变量(python
类配置文件专属特性,目前不支持在 json
、yaml
配置文件中修改 _base_
中定义的变量)
modify_base_var.py
:
1 2 3 4
| _base_ = ['resnet50.py'] a = _base_.model a.type = 'MobileNet'
|
配置文件的导出
在启动训练脚本时,用户可能通过传参的方式来修改配置文件的部分字段,为此我们提供了 dump
接口来导出更改后的配置文件。与读取配置文件类似,用户可以通过 cfg.dump('config.xxx')
来选择导出文件的格式。dump
同样可以导出有继承关系的配置文件,导出的文件可以被独立使用,不再依赖于 _base_
中定义的文件。
基于继承一节定义的 resnet50.py
,我们将其加载后导出:
1 2
| cfg = Config.fromfile('resnet50.py') cfg.dump('resnet50_dump.py')
|
1 2 3
| resnet50_dump.py optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001) model = dict(type='ResNet', depth=50)
|
类似的,我们可以导出 json、yaml 格式的配置文件
1 2 3 4 5 6 7 8 9
| resnet50_dump.yaml model: depth: 50 type: ResNet optimizer: lr: 0.02 momentum: 0.9 type: SGD weight_decay: 0.0001
|
高级用法:以后再说