MMAction2 学习笔记 (三)——通用工具使用及网络DIY (下)

MMAction2 学习笔记 (三)——通用工具使用及网络DIY (下)

0- 写在前面

继续上一篇骨骼关键点识别算法相关内容的学习,今天学习通用领域的工具使用以及网络的DIY
目前正在实习,具体的内容方向是基于骨骼关键点的动作识别(skeleton based action recognition)。 在经过了多多的调研之后,觉得目前还是mmaction2平台最香,因此希望后续工作(包括效果的验证、实验、对比等等)包括前期的端到端工作都在mmaction2工具箱上展开。

学习mmaction2的目的是能够借助该平台对数据集、算法、演示、流程、搭建等等环节都能够理解的更加清晰和透彻(毕竟目前是一个小白),为后续自己的真正实际问题解决做准备。

本篇文章主要是基于mmaction2的中文教程(链接见文末),此外还有一些参考的文章以及github库中的注释性教程。

1- MMAction2 简介

在这里插入图片描述

mmaction2是商汤和港中文联合研发的一个基于pytorch框架的人体动作识别的深度学习开源工具库,可以提供包括行为识别(分类)、时序动作检测、时空动作检测、骨骼动作识别(分类)等等多种子类问题的算法框架,包括数据集等等,可以非常方便的使用。
mmaction2 和广为人知的检测工具库mmdetection 一样,都属于open-mmlab 工具箱下属的一个模块,目前仍在不断更新与拓展功能及算法,可以预见的是,其在人体行为识别方面将被更多人使用。

2- 添加新模块

《先放参考链接》
在本教程中,我们将介绍一些有关如何为该项目定制优化器,开发新组件,以及添加新的学习率调整器(更新器)的方法。

2.1 自定义优化器

CopyOfSGD 是自定义优化器的一个例子,写在 mmaction/core/optimizer/copy_of_sgd.py 文件中。 更一般地,可以根据如下方法自定义优化器。

假设添加的优化器名为 MyOptimizer,它有 abc 三个参数。 用户需要首先实现一个新的优化器文件,如 mmaction/core/optimizer/my_optimizer.py

from mmcv.runner import OPTIMIZERS
from torch.optim import Optimizer

@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):

    def __init__(self, a, b, c):

然后添加这个模块到 mmaction/core/optimizer/__init__.py 中,从而让注册器可以找到这个新的模块并添加它:

from .my_optimizer import MyOptimizer

之后,用户便可以在配置文件的 optimizer 字段中使用 MyOptimizer。 在配置中,优化器由 optimizer 字段所定义,如下所示:

optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)

用户可以直接根据 PyTorch API 文档 对参数进行直接设置。

2.2 自定义优化器构造器

某些模型可能对不同层的参数有特定的优化设置,例如 BatchNorm 层的梯度衰减。 用户可以通过自定义优化器构造函数来进行那些细粒度的参数调整。

用户可以编写一个基于 DefaultOptimizerConstructor 的新的优化器构造器, 并且重写 add_params(self, params, module) 方法。

一个自定义优化器构造器的例子是 TSMOptimizerConstructor。 更具体地,可以如下定义定制的优化器构造器。

mmaction/core/optimizer/my_optimizer_constructor.py

from mmcv.runner import OPTIMIZER_BUILDERS, DefaultOptimizerConstructor

@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor(DefaultOptimizerConstructor):

mmaction/core/optimizer/__init__.py

from .my_optimizer_constructor import MyOptimizerConstructor

之后便可在配置文件的 optimizer 域中使用 MyOptimizerConstructor

# 优化器
optimizer = dict(
    type='SGD',
    constructor='MyOptimizerConstructor',
    paramwise_cfg=dict(fc_lr5=True),
    lr=0.02,
    momentum=0.9,
    weight_decay=0.0001)

2.3开发新组件

MMAction2 将模型组件分为 4 种基础模型:

  1. 识别器(recognizer):整个识别器模型流水线,通常包含一个主干网络(backbone)和分类头(cls_head)。

  2. 主干网络(backbone):通常为一个用于提取特征的 FCN 网络,例如 ResNet,BNInception。

  3. 分类头(cls_head):用于分类任务的组件,通常包括一个带有池化层的 FC 层。

  4. 时序检测器(localizer):用于时序检测的模型,目前有的检测器包含 BSN,BMN,SSN。

  • 添加新的backbone
    这里以 TSN 为例,说明如何开发新的组件。
    1- 创建新文件 mmaction/models/backbones/resnet.py
import torch.nn as nn

from ..builder import BACKBONES

@BACKBONES.register_module()
class ResNet(nn.Module):

    def __init__(self, arg1, arg2):
        pass

    def forward(self, x):  # 应该返回一个元组
        pass

    def init_weights(self, pretrained=None):
        pass

2- 在 mmaction/models/backbones/__init__.py 中导入模型

from .resnet import ResNet

3- 在配置文件中使用它

model = dict(
    ...
    backbone=dict(
        type='ResNet',
        arg1=xxx,
        arg2=xxx),
)
  • 添加新的heads
    这里以 TSNHead 为例,说明如何开发新的 head
    1- 创建新文件 mmaction/models/heads/tsn_head.py
    可以通过继承 BaseHead 编写一个新的分类头, 并重写 init_weights(self)forward(self, x) 方法
from ..builder import HEADS
from .base import BaseHead


@HEADS.register_module()
class TSNHead(BaseHead):

    def __init__(self, arg1, arg2):
        pass

    def forward(self, x):
        pass

    def init_weights(self):
        pass

2- 在 mmaction/models/heads/__init__.py 中导入模型

from .tsn_head import TSNHead

3- 在配置文件中使用它

model = dict(
    ...
    cls_head=dict(
        type='TSNHead',
        num_classes=400,
        in_channels=2048,
        arg1=xxx,
        arg2=xxx),
  • 添加新的loss function
    假设用户想添加新的 loss 为 MyLoss。为了添加一个新的损失函数,需要在 mmaction/models/losses/my_loss.py 下进行实现。
import torch
import torch.nn as nn

from ..builder import LOSSES

def my_loss(pred, target):
    assert pred.size() == target.size() and target.numel() > 0
    loss = torch.abs(pred - target)
    return loss


@LOSSES.register_module()
class MyLoss(nn.Module):

    def forward(self, pred, target):
        loss = my_loss(pred, target)
        return loss

之后,用户需要把它添加进 mmaction/models/losses/__init__.py

from .my_loss import MyLoss, my_loss

为了使用它,需要修改 loss_xxx 域。由于 MyLoss 用户识别任务,可以把它作为边界框损失 loss_bbox

loss_bbox=dict(type='MyLoss'))

2.4 添加新的学习率调节器

构造学习率更新器(即 PyTorch 中的 “scheduler”)的默认方法是修改配置,例如:

...
lr_config = dict(policy='step', step=[20, 40])
...

train.py 的 api 中,它会在以下位置注册用于学习率更新的钩子:

...
    runner.register_training_hooks(
        cfg.lr_config,
        optimizer_config,
        cfg.checkpoint_config,
        cfg.log_config,
        cfg.get('momentum_config', None))
...

到目前位置,所有支持的更新器可参考 mmcv, 但如果用户想自定义学习率更新器,则需要遵循以下步骤:
1- 首先,在 $MMAction2/mmaction/core/scheduler 编写自定义的学习率更新钩子(LrUpdaterHook)。以下片段是自定义学习率更新器的例子,它使用基于特定比率的学习率 lrs,并在每个 steps 处进行学习率衰减。以下代码段是自定义学习率更新器的例子:

# 在此注册
@HOOKS.register_module()
class RelativeStepLrUpdaterHook(LrUpdaterHook):
    # 该类应当继承于 mmcv.LrUpdaterHook
    def __init__(self, steps, lrs, **kwargs):
        super().__init__(**kwargs)
        assert len(steps) == (len(lrs))
        self.steps = steps
        self.lrs = lrs

    def get_lr(self, runner, base_lr):
        # 仅需要重写该函数
        # 该函数在每个训练周期之前被调用, 并返回特定的学习率.
        progress = runner.epoch if self.by_epoch else runner.iter
        for i in range(len(self.steps)):
            if progress < self.steps[i]:
                return self.lrs[i]

2- 修改配置
在配置文件下替换原先的 lr_config 变量

lr_config = dict(policy='RelativeStep', steps=[20, 40, 60], lrs=[0.1, 0.01, 0.001])

3- 导出模型为onnx格式

《参考链接》
开放式神经网络交换格式(Open Neural Network Exchange,即 ONNX)是一个开放的生态系统,使 AI 开发人员能够随着项目的发展选择正确的工具。

3.1 支持的模型

到目前为止,MMAction2 支持将训练的 pytorch 模型中进行 onnx 导出。支持的模型有:
I3D

TSN

TIN

TSM

R(2+1)D

SLOWFAST

SLOWONLY

BMN

BSN(tem, pem)

3.2 如何使用

对于简单的模型导出,用户可以使用这里的 脚本添加链接描述。 注意,需要安装 onnxonnxruntime 包以进行导出后的验证。

  1. 准备工作
    首先安装onnx
pip install onnx onnxruntime

MMAction2 提供了一个 python 脚本,用于将 MMAction2 训练的 pytorch 模型导出到 ONNX。

python tools/deployment/pytorch2onnx.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--shape ${SHAPE}] \
    [--verify] [--show] [--output-file ${OUTPUT_FILE}]  [--is-localizer] [--opset-version ${VERSION}]

可选参数:
--shape: 模型输入张量的形状。对于 2D 模型(如 TSN),输入形状应当为 $batch $clip $channel $height $width (例如,1 1 3 224 224);对于 3D 模型(如 I3D),输入形状应当为 $batch $clip $channel $time $height $width (如,1 1 3 32 224 224);对于时序检测器如 BSN,每个模块的数据都不相同,请查看对应的 forward 函数。如果没有被指定,它将被置为 1 1 3 224 224。

--verify: 决定是否对导出模型进行验证,验证项包括是否可运行,数值是否正确等。如果没有被指定,它将被置为 False。

--show: 决定是否打印导出模型的结构。如果没有被指定,它将被置为 False。

--output-file: 导出的 onnx 模型名。如果没有被指定,它将被置为 tmp.onnx。

--is-localizer:决定导出的模型是否为时序检测器。如果没有被指定,它将被置为 False。

--opset-version:决定 onnx 的执行版本,MMAction2 推荐用户使用高版本(例如 11 版本)的 onnx 以确保稳定性。如果没有被指定,它将被置为 11。

--softmax: 是否在行为识别器末尾添加 Softmax。如果没有指定,将被置为 False。目前仅支持行为识别器,不支持时序动作检测器。

  1. 行为识别器
    对于行为识别器,可运行:
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --shape $SHAPE --verify
  1. 时序动作检测器
    对于时序动作检测器,可运行:
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --is-localizer --shape $SHAPE --verify

4- 自定义模型运行参数

《参考链接》
在本教程中,我们将介绍如何在运行自定义模型时,进行自定义参数优化方法,学习率调整策略,工作流和钩子的方法。

4.1 定制优化方法

  1. 使用 PyTorch 内置的优化器
    MMAction2 支持 PyTorch 实现的所有优化器,仅需在配置文件中,指定 “optimizer” 字段 例如,如果要使用 “Adam”,则修改如下。
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)

要修改模型的学习率,用户只需要在优化程序的配置中修改 “lr” 即可。 用户可根据 PyTorch API 文档 进行参数设置

例如,如果想使用 Adam 并设置参数为 torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False), 则需要进行如下修改:

optimizer = dict(type='Adam', lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
  1. 定制用户自定义的优化器

具体可见本文第二章中的 “自定义优化器”部分

  1. 定制优化器构造器

具体可见本文第二章中的 “自定义优化器构造器”部分

  1. 额外设定
    优化器没有实现的优化技巧(trick)可通过优化器构造函数(例如,设置按参数的学习率)或钩子来实现。 下面列出了一些可以稳定训练或加快训练速度的常用设置。用户亦可通过为 MMAction2 创建 PR,发布更多设置。

使用梯度裁剪来稳定训练 一些模型需要使用梯度裁剪来剪辑渐变以稳定训练过程。 一个例子如下:

optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))

使用动量调整来加速模型收敛 MMAction2 支持动量调整器根据学习率修改模型的动量,从而使模型收敛更快。 动量调整程序通常与学习率调整器一起使用,例如,以下配置用于3D检测以加速收敛。 更多细节可参考 CyclicLrUpdater 和 CyclicMomentumUpdater。

lr_config = dict(
    policy='cyclic',
    target_ratio=(10, 1e-4),
    cyclic_times=1,
    step_ratio_up=0.4,
)
momentum_config = dict(
    policy='cyclic',
    target_ratio=(0.85 / 0.95, 1),
    cyclic_times=1,
    step_ratio_up=0.4,
)

4.2 定制学习率调整策略

在配置文件中使用默认值的逐步学习率调整,它调用 MMCV 中的 StepLRHook。 此外,也支持其他学习率调整方法,如 CosineAnnealingPoly。 详情可见 这里

Poly:

lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)

ConsineAnnealing:

lr_config = dict(
    policy='CosineAnnealing',
    warmup='linear',
    warmup_iters=1000,
    warmup_ratio=1.0 / 10,
    min_lr_ratio=1e-5)

4.3 定制工作流

默认情况下,MMAction2 推荐用户在训练周期中使用 “EvalHook” 进行模型验证,也可以选择 “val” 工作流模型进行模型验证。

工作流是一个形如 (工作流名, 周期数) 的列表,用于指定运行顺序和周期。其默认设置为:

workflow = [('train', 1)]

其代表要进行一轮周期的训练。 有时,用户可能希望检查有关验证集中模型的某些指标(例如,损失,准确性)。 在这种情况下,可以将工作流程设置为

[('train', 1), ('val', 1)]

从而将迭代运行1个训练时间和1个验证时间。

值得注意的是:
1.在验证周期时不会更新模型参数。

2.配置文件内的关键词 total_epochs 控制训练时期数,并且不会影响验证工作流程。

3.工作流 [('train', 1), ('val', 1)][('train', 1)] 不会改变 EvalHook 的行为。 因为 EvalHookafter_train_epoch 调用,而验证工作流只会影响 after_val_epoch 调用的钩子。 因此,[('train', 1), ('val', 1)][('train', 1)] 的区别在于,runner 在完成每一轮训练后,会计算验证集上的损失。

4.4 定制钩子

  1. 定制用户自定义钩子

创建一个新钩子
这里举一个在 MMAction2 中创建一个新钩子,并在训练中使用它的示例:

from mmcv.runner import HOOKS, Hook


@HOOKS.register_module()
class MyHook(Hook):

    def __init__(self, a, b):
        pass

    def before_run(self, runner):
        pass

    def after_run(self, runner):
        pass

    def before_epoch(self, runner):
        pass

    def after_epoch(self, runner):
        pass

    def before_iter(self, runner):
        pass

    def after_iter(self, runner):
        pass

根据钩子的功能,用户需要指定钩子在训练的每个阶段将要执行的操作,比如 before_runafter_runbefore_epochafter_epochbefore_iterafter_iter

注册新钩子

之后,需要导入 MyHook。假设该文件在 mmaction/core/utils/my_hook.py,有两种办法导入它:

1-修改 mmaction/core/utils/__init__.py 进行导入

新定义的模块应导入到mmaction/core/utils/__init__py中,以便注册表能找到并添加新模块:

from .my_hook import MyHook

2- 使用配置文件中的 custom_imports 变量手动导入

custom_imports = dict(imports=['mmaction.core.utils.my_hook'], allow_failed_imports=False)

修改配置

custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value)
]

还可通过 priority 参数(可选参数值包括 'NORMAL''HIGHEST')设置钩子优先级,如下所示:

custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]

默认情况下,在注册过程中,钩子的优先级设置为 “NORMAL”。

  1. 使用MMCV内置钩子

如果该钩子已在 MMCV 中实现,则可以直接修改配置以使用该钩子,如下所示

mmcv_hooks = [
    dict(type='MMCVHook', a=a_value, b=b_value, priority='NORMAL')
]
  1. 修改默认运行的钩子

有一些常见的钩子未通过 custom_hooks 注册,但在导入 MMCV 时已默认注册,它们是:

log_config

checkpoint_config

evaluation

lr_config

optimizer_config

momentum_config

在这些钩子中,只有 log_config 具有 “VERY_LOW” 优先级,其他钩子具有 “NORMAL” 优先级。 上述教程已经介绍了如何修改 “optimizer_config”,“momentum_config” 和 “lr_config”。 下面介绍如何使用 log_config,checkpoint_config,以及 evaluation 能做什么。

模型权重文件配置

MMCV 的 runner 在这里插入代码片使用 checkpoint_config 来初始化 CheckpointHook

checkpoint_config = dict(interval=1)

用户可以设置 “max_keep_ckpts” 来仅保存少量模型权重文件,或者通过 “save_optimizer” 决定是否存储优化器的状态字典。 更多细节可参考 这里。

日志配置
log_config 包装了多个记录器钩子,并可以设置间隔。 目前,MMCV 支持 WandbLoggerHookMlflowLoggerHookTensorboardLoggerHook。 更多细节可参考这里。

log_config = dict(
    interval=50,
    hooks=[
        dict(type='TextLoggerHook'),
        dict(type='TensorboardLoggerHook')
    ])

验证配置
评估的配置将用于初始化 EvalHook。 除了键 interval 外,其他参数,如 “metrics” 也将传递给 dataset.evaluate()

evaluation = dict(interval=1, metrics='bbox')

参考与相关链接:

mmaction2 github 官网:https://github.com/open-mmlab/mmaction2
官方中文文档教程:https://mmaction2.readthedocs.io/zh_CN/latest/index.html
官方知乎介绍文章 :https://zhuanlan.zhihu.com/p/347705276
mmaction2: 使用自定义数据集训练 PoseC3D 的教程 :https://github.com/open-mmlab/mmaction2/blob/master/configs/skeleton/posec3d/custom_dataset_training.md

你可能感兴趣的:(学习笔记,聚类,深度学习,计算机视觉)