MMCV核心组件Runner
在2.0.0版本以后,Runnner从mmcv中移至mmengine,大体用法不发生改变,调用方式发生如下改变:
1 2
| from mmcv.runner import Runner from mmengine.runner import Runner
|
官方文档提示:
我们希望你在教程中更多地关注整体结构,而非具体模块的实现。这种“自顶向下”的思考方式是我们所倡导的。别担心,之后你将有充足的机会和指引,聚焦于自己想要改进的模块
Runner 是 OpenMMLab 系列框架中训练部分的引擎,其核心功能和特性如下:
- 负责 OpenMMLab 中所有框架的训练过程调度,是一个基础但功能丰富的类
- 支持定制工作流以满足训练过程中各状态自由切换,目前支持训练和验证两个工作流
- 提供了 Epoch 和 Iter 为基础的迭代模式以满足不同场景,例如 MMDetection 默认采用 Epoch (配置文件中相关参数都是以 Epoch 为单位),而 MMSegmentation 默认采用 Iter (配置文件中相关参数都是以 Iter 为单位)
- 配合各类 Hook,对外提供了灵活的扩展能力,注入不同类型的 Hook,就可以在训练过程中以一种优雅的方式实现扩展功能
Runner 分析
Runner 初始化
考虑到 Epoch 和 Iter 模式有很多共有逻辑,为了复用,抽象出一个 BaseRunner。BaseRunner 初始化是一个常规初始化过程,其参数如下:
1 2 3 4 5 6 7 8
| def __init__(self, model, optimizer=None, work_dir=None, logger=None, meta=None, max_iters=None, max_epochs=None):
|
当然这只是主要的参数,看源码我们可以看到构造函数有三十几个参数,Runner
初始化沿袭了mmcv的使用Config初始化的习惯,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| from torch.utils.data import DataLoader, default_collate from torch.optim import Adam from mmengine.runner import Runner
@MODELS.register_module() class MyAwesomeModel(BaseModel): ... @DATASETS.register_module() class MyDataset(Dataset): ... @METRICS.register_module() class Accuracy(BaseMetric): ...
runner = Runner( model=MyAwesomeModel( ...), work_dir='exp/my_awesome_model',
train_dataloader=DataLoader( dataset=MyDataset( ...), train_cfg=dict( by_epoch=True, max_epochs=10, val_begin=2, val_interval=1),
optim_wrapper=dict( optimizer=dict( type=Adam, lr=0.001)), param_scheduler=dict( type='MultiStepLR', by_epoch=True, milestones=[4, 8], gamma=0.1),
val_dataloader=DataLoader( dataset=MyDataset( is_train=False, size=1000), shuffle=False, collate_fn=default_collate, batch_size=1000, pin_memory=True, num_workers=2), val_cfg=dict(), val_evaluator=dict(type=Accuracy),
default_hooks=dict( checkpoint=dict(type='CheckpointHook', interval=1)),
launcher='none', env_cfg=dict( cudnn_benchmark=False, backend='nccl', mp_cfg=dict(mp_start_method='fork')), log_level='INFO',
load_from=None, resume=False )
runner.train()
|
即使不了解实现细节,你也一定大体理解了这个训练流程,并感叹于执行器代码的紧凑与可读性(也许)。这也是 MMEngine 所期望的:结构化、模块化、标准化的训练流程,使得复现更加可靠、对比更加清晰
配置参数项太多了,不知道怎么配置怎么办?
把执行器作为备忘录。需要使用的时候查api官方文档
对比手动构建和注册机制构建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from mmengine.model import BaseModel from mmengine.runner import Runner from mmengine.registry import MODELS
@MODELS.register_module() class MyAwesomeModel(BaseModel): def __init__(self, layers=18, activation='silu'): ...
runner = Runner( model=dict( type='MyAwesomeModel', layers=50, activation='relu'), ... )
model = MyAwesomeModel(layers=18, activation='relu') runner = Runner( model=model, ... )
|
摘自官方文档:
为什么我要传入字典让 Runner 来构建实例,这样又有什么好处?如果你有产生这样的疑问,那我们就会很自豪的回答:“当然!(没有好处)”。事实上,基于注册机制的构建方式只有在结合配置文件时才会发挥它的最大优势。这里直接传入字典的写法也并非使用执行器的最佳实践。在这里,我们希望你能够通过这个例子读懂并习惯这种写法,方便理解我们马上将要讲到的执行器最佳实践——配置文件。敬请期待!
如果你作为初学者无法立刻理解,使用手动构建的方式依然不失为一种好选择,甚至在小规模使用、试错和调试时是一种更加推荐的方式,因为对于 IDE 更加友好
锐评:配置文件格式小括号叠小括号写得真恶心,而且官方也不想着优化配置文件格式
执行器最佳实践——配置文件
MMEngine 提供了一套支持 Python
语法的、功能强大的配置文件系统。你可以从之前的示例代码中近乎(我们将在下面说明)无缝地转换到配置文件。下面给出一段示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
model = dict(type='MyAwesomeModel', layers=2, activation='relu') work_dir = 'exp/my_awesome_model'
train_dataloader = dict( dataset=dict(type='MyDataset', ...), sampler=dict( type='DefaultSampler', ...) train_cfg = dict( by_epoch=True, ...) optim_wrapper = dict( optimizer=dict( type='Adam', lr=0.001)) param_scheduler = dict( type='MultiStepLR', by_epoch=True, milestones=[4, 8], gamma=0.1)
val_dataloader = dict( dataset=dict(type='MyDataset', ...), sampler=dict( type='DefaultSampler', ...), collate_fn=dict(type='default_collate'), batch_size=1000, ...) val_cfg = dict() val_evaluator = dict(type='Accuracy')
default_hooks = dict( checkpoint=dict(type='CheckpointHook', interval=1)) launcher = 'none' env_cfg = dict( cudnn_benchmark=False, ...) log_level = 'INFO' load_from = None resume = False
|
此时,我们只需要在训练代码中加载配置,然后运行即可
1 2 3 4 5
| from mmengine.config import Config from mmengine.runner import Runner config = Config.fromfile('example_config.py') runner = Runner.from_cfg(config) runner.train()
|
Runner 的使用:
Runner 的使用过程可以分成 4 个步骤:
- Runner 对象初始化
- 注册各类 Hook 到 Runner 中
- 调用 Runner 的 resume 或者 load_checkpoint 方法对权重进行加载
- 运行给定的工作流,此时才真正开启了工作流
1 2 3 4 5 6 7 8 9 10
| from mmengine.runner import Runner
runner = Runner.from_cfg('config.yaml')
runner.train() runner.test()
runner.save_checkpoint('checkpoint.pth') runner.load_checkpoint('checkpoint.pth')
|