MMAction配置简单理解

MMAction配置简单理解

1.编写配置文件

1.1 查看完整的配置信息

# 模板: python tools/analysis/print_config.py /PATH/TO/CONFIG
python tools/analysis/print_config.py configs/recognition/c3d/c3d_sports1m_16x1x1_45e_ucf101_rgb.py

1.2 修改配置信息

1.2.1 通过命令行参数修改配置信息

当用户使用脚本 “tools/train.py” 或者 “tools/test.py” 提交任务时,可以通过指定 --cfg-options 参数来直接修改所使用的配置文件内容。

  • 更新配置文件内的字典

    用户可以按照原始配置中的字典键顺序来指定配置文件的设置。 例如,--cfg-options model.backbone.norm_eval=False 会改变 train 模式下模型主干网络 backbone 中所有的 BN 模块。

  • 更新配置文件内列表的键

    配置文件中,存在一些由字典组成的列表。例如,训练数据前处理流水线 data.train.pipeline 就是 python 列表。 如,[dict(type='SampleFrames'), ...]。如果用户想更改其中的 'SampleFrames''DenseSampleFrames', 可以指定 --cfg-options data.train.pipeline.0.type=DenseSampleFrames

  • 更新列表/元组的值。

    当配置文件中需要更新的是一个列表或者元组,例如,配置文件通常会设置 workflow=[('train', 1)],用户如果想更改, 需要指定 --cfg-options workflow="[(train,1),(val,1)]"。注意这里的引号 " 对于列表/元组数据类型的修改是必要的, 并且 不允许 引号内所指定的值的书写存在空格。

1.2.2 配置文件结构

config/_base_ 文件夹下存在 3 种基本组件类型: 模型(model), 训练策略(schedule), 运行时的默认设置(default_runtime)。 许多方法都可以方便地通过组合这些组件进行实现,如 TSN,I3D,SlowOnly 等。 其中,通过 _base_ 下组件来构建的配置被称为 原始配置primitive)。

对于在同一文件夹下的所有配置文件,MMAction2 推荐只存在 一个 对应的 原始配置 文件。 所有其他的配置文件都应该继承 原始配置 文件,这样就能保证配置文件的最大继承深度为 3。

为了方便理解,MMAction2 推荐用户继承现有方法的配置文件。 例如,如需修改 TSN 的配置文件,用户应先通过 _base_ = '../tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py' 继承 TSN 配置文件的基本结构, 并修改其中必要的内容以完成继承。

如果用户想实现一个独立于任何一个现有的方法结构的新方法,则需要像 configs/recognition, configs/detection 等一样,在 configs/TASK 中建立新的文件夹。

更多详细内容,请参考 mmcv

1.2.3 配置文件命名规则

MMAction2 按照以下风格进行配置文件命名,代码库的贡献者需要遵循相同的命名规则。

{model}_[model setting]_{backbone}_[misc]_{data setting}_[gpu x batch_per_gpu]_{schedule}_{dataset}_{modality}

其中,{xxx} 表示必要的命名域,[yyy] 表示可选的命名域。

  • {model}:模型类型,如 tsni3d 等。

  • [model setting]:一些模型上的特殊设置。

  • {backbone}:主干网络类型,如 r50(ResNet-50)等。

  • [misc]:模型的额外设置或插件,如 dense320pvideo等。

  • {data setting}:采帧数据格式,形如 {clip_len}x{frame_interval}x{num_clips}

  • [gpu x batch_per_gpu]:GPU 数量以及每个 GPU 上的采样。

  • {schedule}:训练策略设置,如 20e 表示 20 个周期(epoch)。

  • {dataset}:数据集名,如 kinetics400mmit等。

  • {modality}:帧的模态,如 rgb, flow等。

1.2.3.1 时序动作检测的配置文件系统

MMAction2 将模块化设计整合到配置文件系统中,以便于执行各种不同的实验。

  • 以 BMN 为例

为了帮助用户理解 MMAction2 的配置文件结构,以及时序动作检测系统中的一些模块,这里以 BMN 为例,给出其配置文件的注释。 对于每个模块的详细用法以及对应参数的选择,请参照 API 文档

# 模型设置
model = dict(  # 模型的配置
    type='BMN',  # 时序动作检测器的类型
    temporal_dim=100,  # 每个视频中所选择的帧数量
    boundary_ratio=0.5,  # 视频边界的决策几率
    num_samples=32,  # 每个候选的采样数
    num_samples_per_bin=3,  # 每个样本的直方图采样数
    feat_dim=400,  # 特征维度
    soft_nms_alpha=0.4,  # soft-NMS 的 alpha 值
    soft_nms_low_threshold=0.5,  # soft-NMS 的下界
    soft_nms_high_threshold=0.9,  # soft-NMS 的上界
    post_process_top_k=100)  # 后处理得到的最好的 K 个 proposal
# 模型训练和测试的设置
train_cfg = None  # 训练 BMN 的超参配置
test_cfg = dict(average_clips='score')  # 测试 BMN 的超参配置

# 数据集设置
dataset_type = 'ActivityNetDataset'  # 训练,验证,测试的数据集类型
data_root = 'data/activitynet_feature_cuhk/csv_mean_100/'  # 训练集的根目录
data_root_val = 'data/activitynet_feature_cuhk/csv_mean_100/'  # 验证集和测试集的根目录
ann_file_train = 'data/ActivityNet/anet_anno_train.json'  # 训练集的标注文件
ann_file_val = 'data/ActivityNet/anet_anno_val.json'  # 验证集的标注文件
ann_file_test = 'data/ActivityNet/anet_anno_test.json'  # 测试集的标注文件

train_pipeline = [  # 训练数据前处理流水线步骤组成的列表
    dict(type='LoadLocalizationFeature'),  # 加载时序动作检测特征
    dict(type='GenerateLocalizationLabels'),  # 生成时序动作检测标签
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到时序检测器中
        keys=['raw_feature', 'gt_bbox'],  # 输入的键
        meta_name='video_meta',  # 元名称
        meta_keys=['video_name']),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['raw_feature']),  # 将被从其他类型转化为 Tensor 类型的特征
    dict(  # ToDataContainer 类的配置
        type='ToDataContainer',  # 将一些信息转入到 ToDataContainer 中
        fields=[dict(key='gt_bbox', stack=False, cpu_only=True)])  # 携带额外键和属性的信息域
]
val_pipeline = [  # 验证数据前处理流水线步骤组成的列表
    dict(type='LoadLocalizationFeature'),  # 加载时序动作检测特征
    dict(type='GenerateLocalizationLabels'),  # 生成时序动作检测标签
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到时序检测器中
        keys=['raw_feature', 'gt_bbox'],  # 输入的键
        meta_name='video_meta',  # 元名称
        meta_keys=[
            'video_name', 'duration_second', 'duration_frame', 'annotations',
            'feature_frame'
        ]),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['raw_feature']),  # 将被从其他类型转化为 Tensor 类型的特征
    dict(  # ToDataContainer 类的配置
        type='ToDataContainer',  # 将一些信息转入到 ToDataContainer 中
        fields=[dict(key='gt_bbox', stack=False, cpu_only=True)])  # 携带额外键和属性的信息域
]
test_pipeline = [  # 测试数据前处理流水线步骤组成的列表
    dict(type='LoadLocalizationFeature'),  # 加载时序动作检测特征
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到时序检测器中
        keys=['raw_feature'],  # 输入的键
        meta_name='video_meta',  # 元名称
        meta_keys=[
            'video_name', 'duration_second', 'duration_frame', 'annotations',
            'feature_frame'
        ]),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['raw_feature']),  # 将被从其他类型转化为 Tensor 类型的特征
]
data = dict(  # 数据的配置
    videos_per_gpu=8,  # 单个 GPU 的批大小
    workers_per_gpu=8,  # 单个 GPU 的 dataloader 的进程
    train_dataloader=dict(  # 训练过程 dataloader 的额外设置
        drop_last=True),  # 在训练过程中是否丢弃最后一个批次
    val_dataloader=dict(  # 验证过程 dataloader 的额外设置
        videos_per_gpu=1),  # 单个 GPU 的批大小
    test_dataloader=dict(  # 测试过程 dataloader 的额外设置
        videos_per_gpu=2),  # 单个 GPU 的批大小
    test=dict(  # 测试数据集的设置
        type=dataset_type,
        ann_file=ann_file_test,
        pipeline=test_pipeline,
        data_prefix=data_root_val),
    val=dict(  # 验证数据集的设置
        type=dataset_type,
        ann_file=ann_file_val,
        pipeline=val_pipeline,
        data_prefix=data_root_val),
    train=dict(  # 训练数据集的设置
        type=dataset_type,
        ann_file=ann_file_train,
        pipeline=train_pipeline,
        data_prefix=data_root))

# 优化器设置
optimizer = dict(
    # 构建优化器的设置,支持:
    # (1) 所有 PyTorch 原生的优化器,这些优化器的参数和 PyTorch 对应的一致;
    # (2) 自定义的优化器,这些优化器在 `constructor` 的基础上构建。
    # 更多细节可参考 "tutorials/5_new_modules.md" 部分
    type='Adam',  # 优化器类型, 参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13 for more details
    lr=0.001,  # 学习率, 参数的细节使用可参考 PyTorch 的对应文档
    weight_decay=0.0001)  # Adam 优化器的权重衰减
optimizer_config = dict(  # 用于构建优化器钩子的设置
    grad_clip=None)  # 大部分的方法不使用梯度裁剪
# 学习策略设置
lr_config = dict(  # 用于注册学习率调整钩子的设置
    policy='step',  # 调整器策略, 支持 CosineAnnealing,Cyclic等方法。更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
    step=7)  # 学习率衰减步长

total_epochs = 9  # 训练模型的总周期数
checkpoint_config = dict(  # 模型权重文件钩子设置,更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py
    interval=1)  # 模型权重文件保存间隔
evaluation = dict(  # 训练期间做验证的设置
    interval=1,  # 执行验证的间隔
    metrics=['AR@AN'])  # 验证方法
log_config = dict(  # 注册日志钩子的设置
    interval=50,  # 打印日志间隔
    hooks=[  # 训练期间执行的钩子
        dict(type='TextLoggerHook'),  # 记录训练过程信息的日志
        # dict(type='TensorboardLoggerHook'),  # 同时支持 Tensorboard 日志
    ])

# 运行设置
dist_params = dict(backend='nccl')  # 建立分布式训练的设置(端口号,多 GPU 通信框架等)
log_level = 'INFO'  # 日志等级
work_dir = './work_dirs/bmn_400x100_2x8_9e_activitynet_feature/'  # 记录当前实验日志和模型权重文件的文件夹
load_from = None  # 从给定路径加载模型作为预训练模型. 这个选项不会用于断点恢复训练
resume_from = None  # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行
workflow = [('train', 1)]  # runner 的执行流. [('train', 1)] 代表只有一个执行流,并且这个名为 train 的执行流只执行一次
output_config = dict(  # 时序检测器输出设置
    out=f'{work_dir}/results.json',  # 输出文件路径
    output_format='json')  # 输出文件格式
1.2.3.2 动作识别的配置文件系统

MMAction2 将模块化设计整合到配置文件系统中,以便执行各类不同实验。

  • 以 TSN 为例

    为了帮助用户理解 MMAction2 的配置文件结构,以及动作识别系统中的一些模块,这里以 TSN 为例,给出其配置文件的注释。 对于每个模块的详细用法以及对应参数的选择,请参照 API 文档

# 模型设置
model = dict(  # 模型的配置
    type='Recognizer2D',  # 动作识别器的类型
    backbone=dict(  # Backbone 字典设置
        type='ResNet',  # Backbone 名
        pretrained='torchvision://resnet50',  # 预训练模型的 url 或文件位置
        depth=50,  # ResNet 模型深度
        norm_eval=False),  # 训练时是否设置 BN 层为验证模式
    cls_head=dict(  # 分类器字典设置
        type='TSNHead',  # 分类器名
        num_classes=400,  # 分类类别数量
        in_channels=2048,  # 分类器里输入通道数
        spatial_type='avg',  # 空间维度的池化种类
        consensus=dict(type='AvgConsensus', dim=1),  # consensus 模块设置
        dropout_ratio=0.4,  # dropout 层概率
        init_std=0.01), # 线性层初始化 std 值
        # 模型训练和测试的设置
    train_cfg=None,  # 训练 TSN 的超参配置
    test_cfg=dict(average_clips=None))  # 测试 TSN 的超参配置

# 数据集设置
dataset_type = 'RawframeDataset'  # 训练,验证,测试的数据集类型
data_root = 'data/kinetics400/rawframes_train/'  # 训练集的根目录
data_root_val = 'data/kinetics400/rawframes_val/'  # 验证集,测试集的根目录
ann_file_train = 'data/kinetics400/kinetics400_train_list_rawframes.txt'  # 训练集的标注文件
ann_file_val = 'data/kinetics400/kinetics400_val_list_rawframes.txt'  # 验证集的标注文件
ann_file_test = 'data/kinetics400/kinetics400_val_list_rawframes.txt'  # 测试集的标注文件
img_norm_cfg = dict(  # 图像正则化参数设置
    mean=[123.675, 116.28, 103.53],  # 图像正则化平均值
    std=[58.395, 57.12, 57.375],  # 图像正则化方差
    to_bgr=False)  # 是否将通道数从 RGB 转为 BGR

train_pipeline = [  # 训练数据前处理流水线步骤组成的列表
    dict(  # SampleFrames 类的配置
        type='SampleFrames',  # 选定采样哪些视频帧
        clip_len=1,  # 每个输出视频片段的帧
        frame_interval=1,  # 所采相邻帧的时序间隔
        num_clips=3),  # 所采帧片段的数量
    dict(  # RawFrameDecode 类的配置
        type='RawFrameDecode'),  # 给定帧序列,加载对应帧,解码对应帧
    dict(  # Resize 类的配置
        type='Resize',  # 调整图片尺寸
        scale=(-1, 256)),  # 调整比例
    dict(  # MultiScaleCrop 类的配置
        type='MultiScaleCrop',  # 多尺寸裁剪,随机从一系列给定尺寸中选择一个比例尺寸进行裁剪
        input_size=224,  # 网络输入
        scales=(1, 0.875, 0.75, 0.66),  # 长宽比例选择范围
        random_crop=False,  # 是否进行随机裁剪
        max_wh_scale_gap=1),  # 长宽最大比例间隔
    dict(  # Resize 类的配置
        type='Resize',  # 调整图片尺寸
        scale=(224, 224),  # 调整比例
        keep_ratio=False),  # 是否保持长宽比
    dict(  # Flip 类的配置
        type='Flip',  # 图片翻转
        flip_ratio=0.5),  # 执行翻转几率
    dict(  # Normalize 类的配置
        type='Normalize',  # 图片正则化
        **img_norm_cfg),  # 图片正则化参数
    dict(  # FormatShape 类的配置
        type='FormatShape',  # 将图片格式转变为给定的输入格式
        input_format='NCHW'),  # 最终的图片组成格式
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到行为识别器中
        keys=['imgs', 'label'],  # 输入的键
        meta_keys=[]),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['imgs', 'label'])  # 将被从其他类型转化为 Tensor 类型的特征
]
val_pipeline = [  # 验证数据前处理流水线步骤组成的列表
    dict(  # SampleFrames 类的配置
        type='SampleFrames',  # 选定采样哪些视频帧
        clip_len=1,  # 每个输出视频片段的帧
        frame_interval=1,  # 所采相邻帧的时序间隔
        num_clips=3,  # 所采帧片段的数量
        test_mode=True),  # 是否设置为测试模式采帧
    dict(  # RawFrameDecode 类的配置
        type='RawFrameDecode'),  # 给定帧序列,加载对应帧,解码对应帧
    dict(  # Resize 类的配置
        type='Resize',  # 调整图片尺寸
        scale=(-1, 256)),  # 调整比例
    dict(  # CenterCrop 类的配置
        type='CenterCrop',  # 中心裁剪
        crop_size=224),  # 裁剪部分的尺寸
    dict(  # Flip 类的配置
        type='Flip',  # 图片翻转
        flip_ratio=0),  # 翻转几率
    dict(  # Normalize 类的配置
        type='Normalize',  # 图片正则化
        **img_norm_cfg),  # 图片正则化参数
    dict(  # FormatShape 类的配置
        type='FormatShape',  # 将图片格式转变为给定的输入格式
        input_format='NCHW'),  # 最终的图片组成格式
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到行为识别器中
        keys=['imgs', 'label'],  # 输入的键
        meta_keys=[]),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['imgs'])  # 将被从其他类型转化为 Tensor 类型的特征
]
test_pipeline = [  # 测试数据前处理流水线步骤组成的列表
    dict(  # SampleFrames 类的配置
        type='SampleFrames',  # 选定采样哪些视频帧
        clip_len=1,  # 每个输出视频片段的帧
        frame_interval=1,  # 所采相邻帧的时序间隔
        num_clips=25,  # 所采帧片段的数量
        test_mode=True),  # 是否设置为测试模式采帧
    dict(  # RawFrameDecode 类的配置
        type='RawFrameDecode'),  # 给定帧序列,加载对应帧,解码对应帧
    dict(  # Resize 类的配置
        type='Resize',  # 调整图片尺寸
        scale=(-1, 256)),  # 调整比例
    dict(  # TenCrop 类的配置
        type='TenCrop',  # 裁剪 10 个区域
        crop_size=224),  # 裁剪部分的尺寸
    dict(  # Flip 类的配置
        type='Flip',  # 图片翻转
        flip_ratio=0),  # 执行翻转几率
    dict(  # Normalize 类的配置
        type='Normalize',  # 图片正则化
        **img_norm_cfg),  # 图片正则化参数
    dict(  # FormatShape 类的配置
        type='FormatShape',  # 将图片格式转变为给定的输入格式
        input_format='NCHW'),  # 最终的图片组成格式
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到行为识别器中
        keys=['imgs', 'label'],  # 输入的键
        meta_keys=[]),  # 输入的元键
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['imgs'])  # 将被从其他类型转化为 Tensor 类型的特征
]
data = dict(  # 数据的配置
    videos_per_gpu=32,  # 单个 GPU 的批大小
    workers_per_gpu=2,  # 单个 GPU 的 dataloader 的进程
    train_dataloader=dict(  # 训练过程 dataloader 的额外设置
        drop_last=True),  # 在训练过程中是否丢弃最后一个批次
    val_dataloader=dict(  # 验证过程 dataloader 的额外设置
        videos_per_gpu=1),  # 单个 GPU 的批大小
    test_dataloader=dict(  # 测试过程 dataloader 的额外设置
        videos_per_gpu=2),  # 单个 GPU 的批大小
    train=dict(  # 训练数据集的设置
        type=dataset_type,
        ann_file=ann_file_train,
        data_prefix=data_root,
        pipeline=train_pipeline),
    val=dict(  # 验证数据集的设置
        type=dataset_type,
        ann_file=ann_file_val,
        data_prefix=data_root_val,
        pipeline=val_pipeline),
    test=dict(  # 测试数据集的设置
        type=dataset_type,
        ann_file=ann_file_test,
        data_prefix=data_root_val,
        pipeline=test_pipeline))
# 优化器设置
optimizer = dict(
    # 构建优化器的设置,支持:
    # (1) 所有 PyTorch 原生的优化器,这些优化器的参数和 PyTorch 对应的一致;
    # (2) 自定义的优化器,这些优化器在 `constructor` 的基础上构建。
    # 更多细节可参考 "tutorials/5_new_modules.md" 部分
    type='SGD',  # 优化器类型, 参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13
    lr=0.01,  # 学习率, 参数的细节使用可参考 PyTorch 的对应文档
    momentum=0.9,  # 动量大小
    weight_decay=0.0001)  # SGD 优化器权重衰减
optimizer_config = dict(  # 用于构建优化器钩子的设置
    grad_clip=dict(max_norm=40, norm_type=2))  # 使用梯度裁剪
# 学习策略设置
lr_config = dict(  # 用于注册学习率调整钩子的设置
    policy='step',  # 调整器策略, 支持 CosineAnnealing,Cyclic等方法。更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
    step=[40, 80])  # 学习率衰减步长
total_epochs = 100  # 训练模型的总周期数
checkpoint_config = dict(  # 模型权重钩子设置,更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py
    interval=5)  # 模型权重文件保存间隔
evaluation = dict(  # 训练期间做验证的设置
    interval=5,  # 执行验证的间隔
    metrics=['top_k_accuracy', 'mean_class_accuracy'],  # 验证方法
    save_best='top_k_accuracy')  # 设置 `top_k_accuracy` 作为指示器,用于存储最好的模型权重文件
log_config = dict(  # 注册日志钩子的设置
    interval=20,  # 打印日志间隔
    hooks=[  # 训练期间执行的钩子
        dict(type='TextLoggerHook'),  # 记录训练过程信息的日志
        # dict(type='TensorboardLoggerHook'),  # 同时支持 Tensorboard 日志
    ])

# 运行设置
dist_params = dict(backend='nccl')  # 建立分布式训练的设置,其中端口号也可以设置
log_level = 'INFO'  # 日志等级
work_dir = './work_dirs/tsn_r50_1x1x3_100e_kinetics400_rgb/'  # 记录当前实验日志和模型权重文件的文件夹
load_from = None  # 从给定路径加载模型作为预训练模型. 这个选项不会用于断点恢复训练
resume_from = None  # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行
workflow = [('train', 1)]  # runner 的执行流. [('train', 1)] 代表只有一个执行流,并且这个名为 train 的执行流只执行一次
1.2.3.3 时空动作检测的配置文件系统

MMAction2 将模块化设计整合到配置文件系统中,以便于执行各种不同的实验。

  • 以 FastRCNN 为例

    为了帮助用户理解 MMAction2 的完整配置文件结构,以及时空检测系统中的一些模块,这里以 FastRCNN 为例,给出其配置文件的注释。 对于每个模块的详细用法以及对应参数的选择,请参照 API 文档

# 模型设置
model = dict(  # 模型的配置
    type='FastRCNN',  # 时空检测器类型
    backbone=dict(  # Backbone 字典设置
        type='ResNet3dSlowOnly',  # Backbone 名
        depth=50, # ResNet 模型深度
        pretrained=None,   # 预训练模型的 url 或文件位置
        pretrained2d=False, # 预训练模型是否为 2D 模型
        lateral=False,  # backbone 是否有侧连接
        num_stages=4, # ResNet 模型阶数
        conv1_kernel=(1, 7, 7), # Conv1 卷积核尺寸
        conv1_stride_t=1, # Conv1 时序步长
        pool1_stride_t=1, # Pool1 时序步长
        spatial_strides=(1, 2, 2, 1)),  # 每个 ResNet 阶的空间步长
    roi_head=dict(  # roi_head 字典设置
        type='AVARoIHead',  # roi_head 名
        bbox_roi_extractor=dict(  # bbox_roi_extractor 字典设置
            type='SingleRoIExtractor3D',  # bbox_roi_extractor 名
            roi_layer_type='RoIAlign',  # RoI op 类型
            output_size=8,  # RoI op 输出特征尺寸
            with_temporal_pool=True), # 时序维度是否要经过池化
        bbox_head=dict( # bbox_head 字典设置
            type='BBoxHeadAVA', # bbox_head 名
            in_channels=2048, # 输入特征通道数
            num_classes=81, # 动作类别数 + 1(背景)
            multilabel=True,  # 数据集是否多标签
            dropout_ratio=0.5)),  # dropout 比率
    # 模型训练和测试的设置
    train_cfg=dict(  # 训练 FastRCNN 的超参配置
        rcnn=dict(  # rcnn 训练字典设置
            assigner=dict(  # assigner 字典设置
                type='MaxIoUAssignerAVA', # assigner 名
                pos_iou_thr=0.9,  # 正样本 IoU 阈值, > pos_iou_thr -> positive
                neg_iou_thr=0.9,  # 负样本 IoU 阈值, < neg_iou_thr -> negative
                min_pos_iou=0.9), # 正样本最小可接受 IoU
            sampler=dict( # sample 字典设置
                type='RandomSampler', # sampler 名
                num=32, # sampler 批大小
                pos_fraction=1, # sampler 正样本边界框比率
                neg_pos_ub=-1,  # 负样本数转正样本数的比率上界
                add_gt_as_proposals=True), # 是否添加 ground truth 为候选
            pos_weight=1.0, # 正样本 loss 权重
            debug=False)), # 是否为 debug 模式
    test_cfg=dict( # 测试 FastRCNN 的超参设置
        rcnn=dict(  # rcnn 测试字典设置
            action_thr=0.002))) # 某行为的阈值

# 数据集设置
dataset_type = 'AVADataset' # 训练,验证,测试的数据集类型
data_root = 'data/ava/rawframes'  # 训练集的根目录
anno_root = 'data/ava/annotations'  # 标注文件目录

ann_file_train = f'{anno_root}/ava_train_v2.1.csv'  # 训练集的标注文件
ann_file_val = f'{anno_root}/ava_val_v2.1.csv'  # 验证集的标注文件

exclude_file_train = f'{anno_root}/ava_train_excluded_timestamps_v2.1.csv'  # 训练除外数据集文件路径
exclude_file_val = f'{anno_root}/ava_val_excluded_timestamps_v2.1.csv'  # 验证除外数据集文件路径

label_file = f'{anno_root}/ava_action_list_v2.1_for_activitynet_2018.pbtxt'  # 标签文件路径

proposal_file_train = f'{anno_root}/ava_dense_proposals_train.FAIR.recall_93.9.pkl'  # 训练样本检测候选框的文件路径
proposal_file_val = f'{anno_root}/ava_dense_proposals_val.FAIR.recall_93.9.pkl'  # 验证样本检测候选框的文件路径

img_norm_cfg = dict(  # 图像正则化参数设置
    mean=[123.675, 116.28, 103.53], # 图像正则化平均值
    std=[58.395, 57.12, 57.375],   # 图像正则化方差
    to_bgr=False) # 是否将通道数从 RGB 转为 BGR

train_pipeline = [  # 训练数据前处理流水线步骤组成的列表
    dict(  # SampleFrames 类的配置
        type='AVASampleFrames',  # 选定采样哪些视频帧
        clip_len=4,  # 每个输出视频片段的帧
        frame_interval=16), # 所采相邻帧的时序间隔
    dict(  # RawFrameDecode 类的配置
        type='RawFrameDecode'),  # 给定帧序列,加载对应帧,解码对应帧
    dict(  # RandomRescale 类的配置
        type='RandomRescale',   # 给定一个范围,进行随机短边缩放
        scale_range=(256, 320)),   # RandomRescale 的短边缩放范围
    dict(  # RandomCrop 类的配置
        type='RandomCrop',   # 给定一个尺寸进行随机裁剪
        size=256),   # 裁剪尺寸
    dict(  # Flip 类的配置
        type='Flip',  # 图片翻转
        flip_ratio=0.5),  # 执行翻转几率
    dict(  # Normalize 类的配置
        type='Normalize',  # 图片正则化
        **img_norm_cfg),  # 图片正则化参数
    dict(  # FormatShape 类的配置
        type='FormatShape',  # 将图片格式转变为给定的输入格式
        input_format='NCTHW',  # 最终的图片组成格式
        collapse=True),   # 去掉 N 梯度当 N == 1
    dict(  # Rename 类的配置
        type='Rename',  # 重命名 key 名
        mapping=dict(imgs='img')),  # 改名映射字典
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['img', 'proposals', 'gt_bboxes', 'gt_labels']),  # 将被从其他类型转化为 Tensor 类型的特征
    dict(  # ToDataContainer 类的配置
        type='ToDataContainer',  # 将一些信息转入到 ToDataContainer 中
        fields=[   # 转化为 Datacontainer 的域
            dict(   # 域字典
                key=['proposals', 'gt_bboxes', 'gt_labels'],  # 将转化为 DataContainer 的键
                stack=False)]),  # 是否要堆列这些 tensor
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到时空检测器中
        keys=['img', 'proposals', 'gt_bboxes', 'gt_labels'],  # 输入的键
        meta_keys=['scores', 'entity_ids']),  # 输入的元键
]

val_pipeline = [  # 验证数据前处理流水线步骤组成的列表
    dict(  # SampleFrames 类的配置
        type='AVASampleFrames',  # 选定采样哪些视频帧
        clip_len=4,  # 每个输出视频片段的帧
        frame_interval=16),  # 所采相邻帧的时序间隔
    dict(  # RawFrameDecode 类的配置
        type='RawFrameDecode'),  # 给定帧序列,加载对应帧,解码对应帧
    dict(  # Resize 类的配置
        type='Resize',  # 调整图片尺寸
        scale=(-1, 256)),  # 调整比例
    dict(  # Normalize 类的配置
        type='Normalize',  # 图片正则化
        **img_norm_cfg),  # 图片正则化参数
    dict(  # FormatShape 类的配置
        type='FormatShape',  # 将图片格式转变为给定的输入格式
        input_format='NCTHW',  # 最终的图片组成格式
        collapse=True),   # 去掉 N 梯度当 N == 1
    dict(  # Rename 类的配置
        type='Rename',  # 重命名 key 名
        mapping=dict(imgs='img')),  # 改名映射字典
    dict(  # ToTensor 类的配置
        type='ToTensor',  # ToTensor 类将其他类型转化为 Tensor 类型
        keys=['img', 'proposals']),  # 将被从其他类型转化为 Tensor 类型的特征
    dict(  # ToDataContainer 类的配置
        type='ToDataContainer',  # 将一些信息转入到 ToDataContainer 中
        fields=[   # 转化为 Datacontainer 的域
            dict(   # 域字典
                key=['proposals'],  # 将转化为 DataContainer 的键
                stack=False)]),  # 是否要堆列这些 tensor
    dict(  # Collect 类的配置
        type='Collect',  # Collect 类决定哪些键会被传递到时空检测器中
        keys=['img', 'proposals'],  # 输入的键
        meta_keys=['scores', 'entity_ids'],  # 输入的元键
        nested=True)  # 是否将数据包装为嵌套列表
]

data = dict(  # 数据的配置
    videos_per_gpu=16,  # 单个 GPU 的批大小
    workers_per_gpu=2,  # 单个 GPU 的 dataloader 的进程
    val_dataloader=dict(   # 验证过程 dataloader 的额外设置
        videos_per_gpu=1),  # 单个 GPU 的批大小
    train=dict(   # 训练数据集的设置
        type=dataset_type,
        ann_file=ann_file_train,
        exclude_file=exclude_file_train,
        pipeline=train_pipeline,
        label_file=label_file,
        proposal_file=proposal_file_train,
        person_det_score_thr=0.9,
        data_prefix=data_root),
    val=dict(     # 验证数据集的设置
        type=dataset_type,
        ann_file=ann_file_val,
        exclude_file=exclude_file_val,
        pipeline=val_pipeline,
        label_file=label_file,
        proposal_file=proposal_file_val,
        person_det_score_thr=0.9,
        data_prefix=data_root))
data['test'] = data['val']    # 将验证数据集设置复制到测试数据集设置

# 优化器设置
optimizer = dict(
    # 构建优化器的设置,支持:
    # (1) 所有 PyTorch 原生的优化器,这些优化器的参数和 PyTorch 对应的一致;
    # (2) 自定义的优化器,这些优化器在 `constructor` 的基础上构建。
    # 更多细节可参考 "tutorials/5_new_modules.md" 部分
    type='SGD',  # 优化器类型, 参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13
    lr=0.2,  # 学习率, 参数的细节使用可参考 PyTorch 的对应文档
    momentum=0.9,  # 动量大小
    weight_decay=0.00001)  # SGD 优化器权重衰减

optimizer_config = dict(  # 用于构建优化器钩子的设置
    grad_clip=dict(max_norm=40, norm_type=2))   # 使用梯度裁剪

lr_config = dict(  # 用于注册学习率调整钩子的设置
    policy='step',  # 调整器策略, 支持 CosineAnnealing,Cyclic等方法。更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
    step=[40, 80],  # 学习率衰减步长
    warmup='linear',  # Warmup 策略
    warmup_by_epoch=True,  # Warmup 单位为 epoch 还是 iteration
    warmup_iters=5,   # warmup 数
    warmup_ratio=0.1)   # 初始学习率为 warmup_ratio * lr

total_epochs = 20  # 训练模型的总周期数
checkpoint_config = dict(  # 模型权重文件钩子设置,更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py
    interval=1)   # 模型权重文件保存间隔
workflow = [('train', 1)]   # runner 的执行流. [('train', 1)] 代表只有一个执行流,并且这个名为 train 的执行流只执行一次
evaluation = dict(  # 训练期间做验证的设置
    interval=1, save_best='mAP@0.5IOU')  # 执行验证的间隔,以及设置 `mAP@0.5IOU` 作为指示器,用于存储最好的模型权重文件
log_config = dict(  # 注册日志钩子的设置
    interval=20,  # 打印日志间隔
    hooks=[  # 训练期间执行的钩子
        dict(type='TextLoggerHook'),  # 记录训练过程信息的日志
    ])

# 运行设置
dist_params = dict(backend='nccl')  # 建立分布式训练的设置,其中端口号也可以设置
log_level = 'INFO'  # 日志等级
work_dir = ('./work_dirs/ava/'  # 记录当前实验日志和模型权重文件的文件夹
            'slowonly_kinetics_pretrained_r50_4x16x1_20e_ava_rgb')
load_from = ('https://download.openmmlab.com/mmaction/recognition/slowonly/'  # 从给定路径加载模型作为预训练模型. 这个选项不会用于断点恢复训练
             'slowonly_r50_4x16x1_256e_kinetics400_rgb/'
             'slowonly_r50_4x16x1_256e_kinetics400_rgb_20200704-a69556c6.pth')
resume_from = None  # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行

1.3 常见问题

1.3.1 配置文件中的中间变量

配置文件中会用到一些中间变量,如 train_pipeline/val_pipeline/test_pipeline, ann_file_train/ann_file_val/ann_file_test, img_norm_cfg 等。

例如,首先定义中间变量 train_pipeline/val_pipeline/test_pipeline,再将上述变量传递到 data。因此,train_pipeline/val_pipeline/test_pipeline 为中间变量

这里也定义了 ann_file_train/ann_file_val/ann_file_testdata_root/data_root_val 为数据处理流程提供一些基本信息。

此外,使用 img_norm_cfg 作为中间变量,构建一些数组增强组件。

...
dataset_type = 'RawframeDataset'
data_root = 'data/kinetics400/rawframes_train'
data_root_val = 'data/kinetics400/rawframes_val'
ann_file_train = 'data/kinetics400/kinetics400_train_list_rawframes.txt'
ann_file_val = 'data/kinetics400/kinetics400_val_list_rawframes.txt'
ann_file_test = 'data/kinetics400/kinetics400_val_list_rawframes.txt'

img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_bgr=False)

train_pipeline = [
    dict(type='SampleFrames', clip_len=32, frame_interval=2, num_clips=1),
    dict(type='RawFrameDecode'),
    dict(type='Resize', scale=(-1, 256)),
    dict(
        type='MultiScaleCrop',
        input_size=224,
        scales=(1, 0.8),
        random_crop=False,
        max_wh_scale_gap=0),
    dict(type='Resize', scale=(224, 224), keep_ratio=False),
    dict(type='Flip', flip_ratio=0.5),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCTHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs', 'label'])
]
val_pipeline = [
    dict(
        type='SampleFrames',
        clip_len=32,
        frame_interval=2,
        num_clips=1,
        test_mode=True),
    dict(type='RawFrameDecode'),
    dict(type='Resize', scale=(-1, 256)),
    dict(type='CenterCrop', crop_size=224),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCTHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs'])
]
test_pipeline = [
    dict(
        type='SampleFrames',
        clip_len=32,
        frame_interval=2,
        num_clips=10,
        test_mode=True),
    dict(type='RawFrameDecode'),
    dict(type='Resize', scale=(-1, 256)),
    dict(type='ThreeCrop', crop_size=256),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCTHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs'])
]

data = dict(
    videos_per_gpu=8,
    workers_per_gpu=2,
    train=dict(
        type=dataset_type,
        ann_file=ann_file_train,
        data_prefix=data_root,
        pipeline=train_pipeline),
    val=dict(
        type=dataset_type,
        ann_file=ann_file_val,
        data_prefix=data_root_val,
        pipeline=val_pipeline),
    test=dict(
        type=dataset_type,
        ann_file=ann_file_val,
        data_prefix=data_root_val,
        pipeline=test_pipeline))

2 如何微调模型

对新数据集上的模型进行微调需要进行两个步骤:

  1. 增加对新数据集的支持。详情请见 教程 3:如何增加新数据集

  2. 修改配置文件。这部分将在本教程中做具体讨论。

2.1 如何增加新数据集

在本教程中,我们将介绍一些有关如何按已支持的数据格式进行数据组织,和组合已有数据集来自定义数据集的方法。

2.1.1 通过重组数据来自定义数据集
2.1.1.1 将数据集重新组织为现有格式

最简单的方法是将数据集转换为现有的数据集格式(RawframeDataset 或 VideoDataset)。

有三种标注文件:

  • 帧标注(rawframe annotation)

    帧数据集(rawframe dataset)标注文件由多行文本组成,每行代表一个样本,每个样本分为三个部分,分别是 帧(相对)文件夹(rawframe directory of relative path), 总帧数(total frames)以及 标签(label),通过空格进行划分

    示例如下:

some/directory-1 163 1
some/directory-2 122 1
some/directory-3 258 2
some/directory-4 234 2
some/directory-5 295 3
some/directory-6 121 3
  • 视频标注(video annotation)

    视频数据集(video dataset)标注文件由多行文本组成,每行代表一个样本,每个样本分为两个部分,分别是 文件(相对)路径(filepath of relative path) 和 标签(label),通过空格进行划分

    示例如下:

    some/path/000.mp4 1
    some/path/001.mp4 1
    some/path/002.mp4 2
    some/path/003.mp4 2
    some/path/004.mp4 3
    some/path/005.mp4 3
    
  • ActivityNet 标注

    ActivityNet 数据集的标注文件是一个 json 文件。每个键是一个视频名,其对应的值是这个视频的元数据和注释。

    示例如下:

    {
      "video1": {
          "duration_second": 211.53,
          "duration_frame": 6337,
          "annotations": [
              {
                  "segment": [
                      30.025882995319815,
                      205.2318595943838
                  ],
                  "label": "Rock climbing"
              }
          ],
          "feature_frame": 6336,
          "fps": 30.0,
          "rfps": 29.9579255898
      },
      "video2": {
          "duration_second": 26.75,
          "duration_frame": 647,
          "annotations": [
              {
                  "segment": [
                      2.578755070202808,
                      24.914101404056165
                  ],
                  "label": "Drinking beer"
              }
          ],
          "feature_frame": 624,
          "fps": 24.0,
          "rfps": 24.1869158879
      }
    }
    

有两种使用自定义数据集的方法:

  • 在线转换

    用户可以通过继承 BaseDataset 基类编写一个新的数据集类,并重写三个抽象类方法: load_annotations(self)evaluate(self, results, metrics, logger)dump_results(self, results, out), 如 RawframeDatasetVideoDatasetActivityNetDataset

  • 本地转换

    用户可以转换标注文件格式为上述期望的格式,并将其存储为 pickle 或 json 文件,然后便可以应用于 RawframeDatasetVideoDatasetActivityNetDataset 中。

数据预处理后,用户需要进一步修改配置文件以使用数据集。 这里展示了以帧形式使用自定义数据集的例子:

configs/task/method/my_custom_config.py 下:

...
# 数据集设定
dataset_type = 'RawframeDataset'
data_root = 'path/to/your/root'
data_root_val = 'path/to/your/root_val'
ann_file_train = 'data/custom/custom_train_list.txt'
ann_file_val = 'data/custom/custom_val_list.txt'
ann_file_test = 'data/custom/custom_val_list.txt'
...
data = dict(
    videos_per_gpu=32,
    workers_per_gpu=2,
    train=dict(
        type=dataset_type,
        ann_file=ann_file_train,
        ...),
    val=dict(
        type=dataset_type,
        ann_file=ann_file_val,
        ...),
    test=dict(
        type=dataset_type,
        ann_file=ann_file_test,
        ...))
...
2.1.1.2 自定义数据集的示例

假设注释在文本文件中以新格式显示,并且图像文件名具有类似 “img_00005.jpg” 的模板。 那么视频注释将以以下形式存储在文本文件 annotation.txt 中。

#文件夹,总帧数,类别
D32_1gwq35E,299,66
-G-5CJ0JkKY,249,254
T4h1bvOd9DA,299,33
4uZ27ivBl00,299,341
0LfESFkfBSw,249,186
-YIsNpBEx6c,299,169

mmaction/datasets/my_dataset.py 中创建新数据集加载数据

import copy
import os.path as osp

import mmcv

from .base import BaseDataset
from .builder import DATASETS


@DATASETS.register_module()
class MyDataset(BaseDataset):

    def __init__(self,
                 ann_file,
                 pipeline,
                 data_prefix=None,
                 test_mode=False,
                 filename_tmpl='img_{:05}.jpg'):
        super(MyDataset, self).__init__(ann_file, pipeline, test_mode)

        self.filename_tmpl = filename_tmpl

    def load_annotations(self):
        video_infos = []
        with open(self.ann_file, 'r') as fin:
            for line in fin:
                if line.startswith("directory"):
                    continue
                frame_dir, total_frames, label = line.split(',')
                if self.data_prefix is not None:
                    frame_dir = osp.join(self.data_prefix, frame_dir)
                video_infos.append(
                    dict(
                        frame_dir=frame_dir,
                        total_frames=int(total_frames),
                        label=int(label)))
        return video_infos

    def prepare_train_frames(self, idx):
        results = copy.deepcopy(self.video_infos[idx])
        results['filename_tmpl'] = self.filename_tmpl
        return self.pipeline(results)

    def prepare_test_frames(self, idx):
        results = copy.deepcopy(self.video_infos[idx])
        results['filename_tmpl'] = self.filename_tmpl
        return self.pipeline(results)

    def evaluate(self,
                 results,
                 metrics='top_k_accuracy',
                 topk=(1, 5),
                 logger=None):
        pass

然后在配置文件中,用户可通过如下修改来使用 MyDataset

dataset_A_train = dict(
    type='MyDataset',
    ann_file=ann_file_train,
    pipeline=train_pipeline
)
2.1.2 通过组合已有数据集来自定义数据集

MMAction2 还支持组合已有数据集以进行训练。 目前,它支持重复数据集(repeat dataset)。

2.1.2.1 重复数据集

MMAction2 使用 RepeatDataset 作为包装器来重复数据集。例如,假设原始数据集为 Dataset_A, 为了重复此数据集,可设置配置如下:

dataset_A_train = dict(
        type='RepeatDataset',
        times=N,
        dataset=dict(  # 这是 Dataset_A 的原始配置
            type='Dataset_A',
            ...
            pipeline=train_pipeline
        )
    )

2.2 修改配置文件

例如,如果用户想要微调 Kinetics-400 数据集的预训练模型到另一个数据集上,如 UCF101,则需要注意 配置文件 中 Head、数据集、训练策略、预训练模型四个部分,下面分别介绍。

2.2.1 修改 Head

cls_head 中的 num_classes 参数需改为新数据集中的类别数。 预训练模型中,除了最后一层外的权重都会被重新利用,因此这个改动是安全的。 例如,UCF101 拥有 101 类行为,因此需要把 400 (Kinetics-400 的类别数) 改为 101。

model = dict(
    type='Recognizer2D',
    backbone=dict(
        type='ResNet',
        pretrained='torchvision://resnet50',
        depth=50,
        norm_eval=False),
    cls_head=dict(
        type='TSNHead',
        num_classes=101,   # 从 400 改为 101
        in_channels=2048,
        spatial_type='avg',
        consensus=dict(type='AvgConsensus', dim=1),
        dropout_ratio=0.4,
        init_std=0.01),
    train_cfg=None,
    test_cfg=dict(average_clips=None))

其中, pretrained='torchvision://resnet50' 表示通过 ImageNet 预训练权重初始化 backbone。 然而,模型微调时的预训练权重一般通过 load_from(而不是 pretrained)指定。

2.2.2 修改数据集

MMAction2 支持 UCF101, Kinetics-400, Moments in Time, Multi-Moments in Time, THUMOS14, Something-Something V1&V2, ActivityNet 等数据集。 用户可将自建数据集转换已有数据集格式。 对动作识别任务来讲,MMAction2 提供了 RawframeDatasetVideoDataset 等通用的数据集读取类,数据集格式相对简单。 以 UCF101RawframeDataset 为例,

# 数据集设置
dataset_type = 'RawframeDataset'
data_root = 'data/ucf101/rawframes_train/'
data_root_val = 'data/ucf101/rawframes_val/'
ann_file_train = 'data/ucf101/ucf101_train_list.txt'
ann_file_val = 'data/ucf101/ucf101_val_list.txt'
ann_file_test = 'data/ucf101/ucf101_val_list.txt'
2.2.3 修改训练策略

通常情况下,设置较小的学习率,微调模型少量训练批次,即可取得较好效果。

# 优化器
optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0001)  # 从 0.01 改为 0.005
optimizer_config = dict(grad_clip=dict(max_norm=40, norm_type=2))
# 学习策略
lr_config = dict(policy='step', step=[20, 40]) # step 与 total_epoch 相适应
total_epochs = 50 # 从 100 改为 50
checkpoint_config = dict(interval=5)
2.2.4 使用预训练模型

若要将预训练模型用于整个网络(主干网络设置中的 pretrained,仅会在主干网络模型上加载预训练参数),可通过 load_from 指定模型文件路径或模型链接,实现预训练权重导入。 MMAction2 在 configs/_base_/default_runtime.py 文件中将 load_from=None 设为默认。由于配置文件的可继承性,用户可直接在下游配置文件中设置 load_from 的值来进行更改。

# 将预训练模型用于整个 TSN 网络
load_from = 'https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmaction/mmaction-v1/recognition/tsn_r50_1x1x3_100e_kinetics400_rgb/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'  # 模型路径可以在 model zoo 中找到

4 如何设计数据处理流程

在本教程中,我们将介绍一些有关数据前处理流水线设计的方法,以及如何为项目自定义和扩展自己的数据流水线。

4.1 数据前处理流水线设计

按照惯例,MMAction2 使用 DatasetDataLoader 实现多进程数据加载。 Dataset 返回一个字典,作为模型的输入。 由于动作识别和时序动作检测的数据大小不一定相同(图片大小,边界框大小等),MMAction2 使用 MMCV 中的 DataContainer 收集和分配不同大小的数据, 详情可见 这里

“数据前处理流水线” 和 “数据集构建” 是相互解耦的。通常,“数据集构建” 定义如何处理标注文件,“数据前处理流水线” 定义数据加载、预处理、格式化等功能(后文将详细介绍)。 数据前处理流水线由一系列相互解耦的操作组成。每个操作都输入一个字典(dict),新增/更新/删除相关字段,最终输出该字典,作为下一个操作的输入。

我们在下图中展示了一个典型的流水线。 蓝色块是流水线操作。 随着流水线的深入,每个操作都可以向结果字典添加新键(标记为绿色)或更新现有键(标记为橙色)。

https://github.com/open-mmlab/mmaction2/raw/master/resources/data_pipeline.png

这些操作分为数据加载,数据预处理和数据格式化。

这里以 TSN 的数据前处理流水线为例:

img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_bgr=False)
train_pipeline = [
    dict(type='SampleFrames', clip_len=1, frame_interval=1, num_clips=3),
    dict(type='RawFrameDecode', io_backend='disk'),
    dict(type='Resize', scale=(-1, 256)),
    dict(
        type='MultiScaleCrop',
        input_size=224,
        scales=(1, 0.875, 0.75, 0.66),
        random_crop=False,
        max_wh_scale_gap=1),
    dict(type='Resize', scale=(224, 224), keep_ratio=False),
    dict(type='Flip', flip_ratio=0.5),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs', 'label'])
]
val_pipeline = [
    dict(
        type='SampleFrames',
        clip_len=1,
        frame_interval=1,
        num_clips=3,
        test_mode=True),
    dict(type='RawFrameDecode', io_backend='disk'),
    dict(type='Resize', scale=(-1, 256)),
    dict(type='CenterCrop', crop_size=224),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs'])
]
test_pipeline = [
    dict(
        type='SampleFrames',
        clip_len=1,
        frame_interval=1,
        num_clips=25,
        test_mode=True),
    dict(type='RawFrameDecode', io_backend='disk'),
    dict(type='Resize', scale=(-1, 256)),
    dict(type='TenCrop', crop_size=224),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs'])
]

MMAction2 也支持一些 lazy 操作符。 Lazy 操作记录如何处理数据,但是它会推迟对原始数据的处理,直到进入 Fuse 阶段。 具体而言,lazy 操作符避免了对原始数据的频繁读取和修改操作,只在最后的 Fuse 阶段中对原始数据进行了一次处理,从而加快了数据预处理速度,因此,推荐用户使用本功能。

这是使用 lazy 运算符的数据前处理流水线的例子:

train_pipeline = [
    dict(type='SampleFrames', clip_len=32, frame_interval=2, num_clips=1),
    dict(type='RawFrameDecode', decoding_backend='turbojpeg'),
    # 以下三个 lazy 操作符仅处理帧的 bbox 而不修改原始数据。
    dict(type='Resize', scale=(-1, 256), lazy=True),
    dict(
        type='MultiScaleCrop',
        input_size=224,
        scales=(1, 0.8),
        random_crop=False,
        max_wh_scale_gap=0,
        lazy=True),
    dict(type='Resize', scale=(224, 224), keep_ratio=False, lazy=True),
    # lazy 操作符 “Flip” 仅记录是否应该翻转框架和翻转方向。
    dict(type='Flip', flip_ratio=0.5, lazy=True),
    # 在 Fuse 阶段处理一次原始数据
    dict(type='Fuse'),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='FormatShape', input_format='NCTHW'),
    dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
    dict(type='ToTensor', keys=['imgs', 'label'])
]

本节将所有操作分为数据加载、数据预处理、数据格式化三类,列出每个操作 新增/更新/删除 的相关字典字段,其中 * 代表所对应的键值不一定会被影响。

数据加载

SampleFrames
  • 新增: frame_inds, clip_len, frame_interval, num_clips, *total_frames
DenseSampleFrames
  • 新增: frame_inds, clip_len, frame_interval, num_clips, *total_frames
PyAVDecode
  • 新增: imgs, original_shape

  • 更新: *frame_inds

DecordDecode
  • 新增: imgs, original_shape

  • 更新: *frame_inds

OpenCVDecode
  • 新增: imgs, original_shape

  • 更新: *frame_inds

RawFrameDecode
  • 新增: imgs, original_shape

  • 更新: *frame_inds

数据预处理

RandomCrop
  • 新增: crop_bbox, img_shape

  • 更新: imgs

RandomResizedCrop
  • 新增: crop_bbox, img_shape

  • 更新: imgs

MultiScaleCrop
  • 新增: crop_bbox, img_shape, scales

  • 更新: imgs

Resize
  • 新增: img_shape, keep_ratio, scale_factor

  • 更新: imgs

Flip
  • 新增: flip, flip_direction

  • 更新: imgs, label

Normalize
  • 新增: img_norm_cfg

  • 更新: imgs

CenterCrop
  • 新增: crop_bbox, img_shape

  • 更新: imgs

ThreeCrop
  • 新增: crop_bbox, img_shape

  • 更新: imgs

TenCrop
  • 新增: crop_bbox, img_shape

  • 更新: imgs

数据格式化

ToTensor
  • 更新: specified by keys.
ImageToTensor
  • 更新: specified by keys.
Transpose
  • 更新: specified by keys.
Collect
  • 新增: img_metas (所有需要的图像元数据,会被在此阶段整合进 meta_keys 键值中)

  • 删除: 所有没有被整合进 keys 的键值

值得注意的是,第一个键,通常是 imgs,会作为主键用来计算批大小。

FormatShape
  • 新增: input_shape

  • 更新: imgs

扩展和使用自定义流水线

  1. 在任何文件写入一个新的处理流水线,如 my_pipeline.py。它以一个字典作为输入并返回一个字典

    from mmaction.datasets import PIPELINES
    
    @PIPELINES.register_module()
    class MyTransform:
    
        def __call__(self, results):
            results['key'] = value
            return results
    
  2. 导入新类

    from .my_pipeline import MyTransform
    
  3. 在配置文件使用它

    img_norm_cfg = dict(
         mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
    train_pipeline = [
        dict(type='DenseSampleFrames', clip_len=8, frame_interval=8, num_clips=1),
        dict(type='RawFrameDecode', io_backend='disk'),
        dict(type='MyTransform'),       # 使用自定义流水线操作
        dict(type='Normalize', **img_norm_cfg),
        dict(type='FormatShape', input_format='NCTHW'),
        dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
        dict(type='ToTensor', keys=['imgs', 'label'])
    ]
    

5 添加新模块

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

5.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 文档 对参数进行直接设置。

5.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)

5.3 开发新组件

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

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

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

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

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

5.3.1 添加新的 backbones

这里以 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),
    )
    
5.3.2 添加新的 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),
    
5.3.3 添加新的 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'))
5.3.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]
  1. 修改配置

在配置文件下替换原先的 lr_config 变量

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

更多例子可参考 mmcv

6 如何导出模型为 onnx 格式

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

6.1 支持的模型

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

  • I3D

  • TSN

  • TIN

  • TSM

  • R(2+1)D

  • SLOWFAST

  • SLOWONLY

  • BMN

  • BSN(tem, pem)

6.3 如何使用

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

6.3.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。目前仅支持行为识别器,不支持时序动作检测器。

6.3.2 行为识别器

对于行为识别器,可运行:

python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --shape $SHAPE --verify
6.3.3 时序动作检测器

对于时序动作检测器,可运行:

python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --is-localizer --shape $SHAPE --verify

如果发现提供的模型权重文件没有被成功导出,或者存在精度损失,可以在本 repo 下提出问题(issue)。

7 如何自定义模型运行参数

在本教程中,我们将介绍如何在运行自定义模型时,进行自定义参数优化方法,学习率调整策略,工作流和钩子的方法。https://gitee.com/funweb/mmaction2/blob/master/docs_zh_CN/tutorials/7_customize_runtime.md#验证配置)

7.1 定制优化方法

7.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)
7.1.2 定制用户自定义的优化器
7.1.2.1 定义一个新的优化器

一个自定义的优化器可根据如下规则进行定制

假设用户想添加一个名为 MyOptimzer 的优化器,其拥有参数 a, bc, 可以创建一个名为 mmaction/core/optimizer 的文件夹,并在目录下的文件进行构建,如 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):
7.1.2.2 注册优化器

要找到上面定义的上述模块,首先应将此模块导入到主命名空间中。有两种方法可以实现它。

  • 修改 mmaction/core/optimizer/__init__.py 来进行调用

    新定义的模块应导入到 mmaction/core/optimizer/__init__.py 中,以便注册器能找到新模块并将其添加:

from .my_optimizer import MyOptimizer
  • 在配置中使用 custom_imports 手动导入
custom_imports = dict(imports=['mmaction.core.optimizer.my_optimizer'], allow_failed_imports=False)

mmaction.core.optimizer.my_optimizer 模块将会在程序开始阶段被导入,MyOptimizer 类会随之自动被注册。 注意,只有包含 MyOptmizer 类的包会被导入。mmaction.core.optimizer.my_optimizer.MyOptimizer 不会 被直接导入。

7.1.2.3 在配置文件中指定优化器

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

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

要使用自定义的优化器,可以将该字段更改为

optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
7.1.3 定制优化器构造器

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

from mmcv.runner.optimizer import OPTIMIZER_BUILDERS


@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor:

    def __init__(self, optimizer_cfg, paramwise_cfg=None):
        pass

    def __call__(self, model):

        return my_optimizer

默认的优化器构造器被创建于, 可被视为新优化器构造器的模板。

7.1.4 额外设定

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

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

    optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
    
  • 使用动量调整来加速模型收敛 MMAction2 支持动量调整器根据学习率修改模型的动量,从而使模型收敛更快。 动量调整程序通常与学习率调整器一起使用,例如,以下配置用于3D检测以加速收敛。 更多细节可参考 CyclicLrUpdaterCyclicMomentumUpdater

    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,
    )
    

7.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)
    

7.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 在完成每一轮训练后,会计算验证集上的损失。

7.4 定制钩子

7.4.1 定制用户自定义钩子
7.4.1.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

7.4.1.2 注册新钩子

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

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

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

from .my_hook import MyHook
  • 使用配置文件中的 custom_imports 变量手动导入
custom_imports = dict(imports=['mmaction.core.utils.my_hook'], allow_failed_imports=False)
7.4.1.3 修改配置
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”。

7.4.2 使用 MMCV 内置钩子

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

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

有一些常见的钩子未通过 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 能做什么。

7.4.3.1 模型权重文件配置

MMCV 的 runner 使用 checkpoint_config 来初始化 CheckpointHook

checkpoint_config = dict(interval=1)

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

7.4.3.2 日志配置

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

log_config = dict(
    interval=50,
    hooks=[
        dict(type='TextLoggerHook'),
        dict(type='TensorboardLoggerHook')
    ])
7.4.3.3 验证配置

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

evaluation = dict(interval=1, metrics='bbox')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

或许,这就是梦想吧!

如果对你有用,欢迎打赏。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值