原创:赵明明
做目标检测的同学应该已经接触过或者正要接触MMDetection,当你接触到MMDeteciton的时候,你会发现,它的代码有点奇怪:它不写train函数,不写eval函数,反而配置了一个runner,把训练过程配置到runner中,运行runner,就是运行train/eval函数.如下:
![](https://img.haomeiwen.com/i27533639/0a05dc3f35f76416.png)
上图第84行,就是对runner的运行。在运行之前的第62行,第78行,分别将模型model,优化器optimizer,日志logger,学习率调整策略配置入runner内。配置好后,运行runner就可以i实现模型的训练了。
为什么要使用runner?
我认为最大的特点就是:方便了算法研究,同学们可以少花一点时间在写代码上,把更多的精力花在模型的设计上。runner里面集成了图像和视频处理,图像和标注结果的可视化,对训练过程记时统计,以及高质量的常见的GPU算子等,也集成了多种CNN网络机构,基本上常用的网络结构都已经集成在这个模型里了。
由于mmcv的便利性,基于mmcv的开发已经支持了CV各个领域的开源项目:
- 有MMClassification,图像分类工具箱;
- MMDetection图像检测工具箱;
- MMDetection3D新一代通用3D目标检测平台,
- 以及MMSegmentation图像分割工具箱。
- 除了经典的视觉任务,还实现了视频理解工具箱MMAction2,
- 目标感知平台MMTracking,
- 姿态估计工具箱MMPose,
- 文字检测工具箱MMOCR,
- 图像视频编辑工具箱MMEditing等一些常见的应用场景
- 最后,也有部署方面的模型压缩工具箱MMRazor,模型部署框架MMDeploy.
为何mmcv可以支持开发从模型设计,到模型应用,再到模型部署这样全方位的工具箱?
原因有很多,其中配置型的runner功不可没。
下面咱们就了解下runner的思路
runner通过配置信息构建了训练工作流,测试工作流。比如设置 workflow=[('train',200),('val',1)],表示循环的训练200个epoch,然后验证1个epoch.
构建逻辑如下:
while curr_epoch < max_epochs:
# 遍历用户设置的工作流,例如 workflow = [('train', 200),('val', 1)]
for i, flow in enumerate(workflow):
# mode 是工作流函数,例如 train, epochs 是迭代次数
# mode要么是 要么是train,要么是val
mode, epochs = flow
# 要么调用 self.train(),要么调用 self.val()
epoch_runner = getattr(self, mode)
# 按照设定的epoches=200运行对应工作流函数self.train()
for _ in range(epochs):
epoch_runner(data_loaders[i], **kwargs)
那么如上代码中,调用的self.train()函数即epoch_runner内部是怎样的?如下:
# epoch_runner目前可以是 train 或者 val
# 这里用train函数举例子
def train(self, data_loader, **kwargs):
# 遍历 dataset,
for i, data_batch in enumerate(data_loader):
# 运行预先配置在训练前要执行的功能
self.call_hook('before_train_iter')
# run_iter运行迭代一次的代码,这个 run_iter函数内部其实就是我们原来常写的那种forward,loss,backword,optim.step等四句。
# 当把train_mode=False时,叫表示验证val了
self.run_iter(data_batch, train_mode=True, **kwargs)
# 运行预先配置在训练后要执行的功能
self.call_hook('after_train_iter')
# 凡是call_hook都是预先配置好的
self.call_hook('after_train_epoch')
也就是说, runner把我们常用的训练四步:forward,loss,backword,optim.step变成默认项,然后又给了我们常用的功能留了配置的接口。比如训练过程中需要保存模型的功能,断点重训的功能,还可以自定义的一些特殊的操作。
大概了解了runner的设计思路,下面我们具体分析一个简单的例子,更细节的去认识一下runner:
以下代码具体地址:https://gitee.com/anjiang2020_admin/mmcv/blob/master/examples/train.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR10
from mmcv.parallel import MMDataParallel
from mmcv.runner import EpochBasedRunner
from mmcv.utils import get_logger
# 模型类的编写
# 可以写的很复杂,也可以写的很简单
# 这里就简单也一个5层的网络LeNet
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
self.loss_fn = nn.CrossEntropyLoss()
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
# train_step函数是必须要写的
# runner中会调用此函数,完成训练任务
# 也就是说,当把model配置入runner时,
# 就相当于把train_step配置给runner了
# runner只是个调度器,真正的训练核心步骤,还是我们自己写的。
def train_step(self, data, optimizer):
images, labels = data
predicts = self(images) # -> self.__call__() -> self.forward()
loss = self.loss_fn(predicts, labels)
return {'loss': loss}
if __name__ == '__main__':
# 声明一个我们定义的模型Model类型的变量model
model = Model()
# 如果可以,尽量使用gpu并行训练
if torch.cuda.is_available():
# only use gpu:0 to train
# Solved issue https://github.com/open-mmlab/mmcv/issues/1470
model = MMDataParallel(model.cuda(), device_ids=[0])
# dataset and dataloader
# 数据集准备 与数据加载器的准备
# transform:数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 获取cifar10数据集,读入trainset,如果磁盘上没有,就自动从网络下载
trainset = CIFAR10(
root='data', train=True, download=True, transform=transform)
# trainset放入数据加载器trainloader
# 这样数据加载器trainloader就准备好了
trainloader = DataLoader(
trainset, batch_size=128, shuffle=True, num_workers=2)
# 准备优化器 optimizer
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
# 准备日志管理器
logger = get_logger('mmcv')
# runner is a scheduler to manage the training
# 将待训练模型model,优化器optimizer,日志管理器配置到runner中
# runner此时已经包含了训练的大的框架:前向计算,反向传播,日志记录
runner = EpochBasedRunner(
model,
optimizer=optimizer,
work_dir='./work_dir',
logger=logger,
max_epochs=4)
# learning rate scheduler config
# 配置优化器中的训练学习率衰减策略
lr_config = dict(policy='step', step=[2, 3])
# configuration of optimizer
# 配置优化器中是否使用梯度剪裁grad_clip
optimizer_config = dict(grad_clip=None)
# configuration of saving checkpoints periodically
# 配置间隔interval个epoch保存一次训练参数
checkpoint_config = dict(interval=1)
# save log periodically and multiple hooks can be used simultaneously
log_config = dict(interval=100, hooks=[dict(type='TextLoggerHook')])
# register hooks to runner and those hooks will be invoked automatically
# 使用register_training_hooks函数,将以上文件配置到runner中
# register_training_hooks函数让我们可以自己定以训练过程中的细节
runner.register_training_hooks(
lr_config=lr_config,
optimizer_config=optimizer_config,
checkpoint_config=checkpoint_config,
log_config=log_config)
# 完成以上配置后,就可以运行了
runner.run([trainloader], [('train', 1)])
可以看到,这里的主要步骤是:
- 建立runner时,将模型本身,优化器本身配置入runner
- 用runner的成员函数register_training_hooks对runner的训练细节进行配置,如学习路具体用哪种策略来调整,是否使用梯度剪裁等。
至此,我们看到,mmcv中的runner是一个调度器,我们只要把与训练模型的相关要素配置给它,它就可以帮我们把各种要素利用起来,完成我们的既定的训练或者测试的动作。
因为以上关于分类的工具箱,目标检测,图像分割,3D目标检测,视频理解,感知,ORC,姿态检测等工具箱都是用runner的思路为基础配置出来的。
所以说,MMCV是众多CV工具箱依赖的工具箱,推荐在需要人肉调参时使用,可以节省coding的时间,减少bug,总体提升效率。
mmcv源码下载链接以及安装使用方式:
源码链接:
https://gitee.com/anjiang2020_admin/mmcv.git
安装使用方式:
cpu安装:
pip install mmcv
gpu安装:
pip install mmcv-full
由于gpu加速库的版本与pytorch版本的适配性,gpu安装稍微繁琐些,详细细节在这里可以查询到:
https://gitee.com/-/ide/project/anjiang2020_admin/SEPC/edit/master/-/README.md
安装完成后,用以下命令使用:
git clone https://gitee.com/anjiang2020_admin/mmcv.git
cd mmcv/examples
python train.py
这个train.py里就是本文主要讲解的代码例子
运行起来后,它会自己下载数据,自己启动训练,如下图所示:
![](https://img.haomeiwen.com/i27533639/d0a58fd054c71a79.png)
关于MMDetection的讲解到这里就结束了,大家快动手操练起来吧!
网友评论