"src/vscode:/vscode.git/clone" did not exist on "60a860b2e97f15176780eac48e76a2377a85dd4d"
Commit 37437e80 authored by sunxx1's avatar sunxx1
Browse files

Merge branch 'sun_22.10' into 'main'

Sun 22.10

See merge request dcutoolkit/deeplearing/dlexamples_new!54
parents 8442f072 701c0060
# 基础教程
本文档提供 MMAction2 相关用法的基本教程。对于安装说明,请参阅 [安装指南](install.md)
<!-- TOC -->
- [基础教程](#%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B)
- [数据集](#%E6%95%B0%E6%8D%AE%E9%9B%86)
- [使用预训练模型进行推理](#%E4%BD%BF%E7%94%A8%E9%A2%84%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B%E8%BF%9B%E8%A1%8C%E6%8E%A8%E7%90%86)
- [测试某个数据集](#%E6%B5%8B%E8%AF%95%E6%9F%90%E4%B8%AA%E6%95%B0%E6%8D%AE%E9%9B%86)
- [使用高级 API 对视频和帧文件夹进行测试](#%E4%BD%BF%E7%94%A8%E9%AB%98%E7%BA%A7-api-%E5%AF%B9%E8%A7%86%E9%A2%91%E5%92%8C%E5%B8%A7%E6%96%87%E4%BB%B6%E5%A4%B9%E8%BF%9B%E8%A1%8C%E6%B5%8B%E8%AF%95)
- [如何建立模型](#%E5%A6%82%E4%BD%95%E5%BB%BA%E7%AB%8B%E6%A8%A1%E5%9E%8B)
- [使用基本组件建立模型](#%E4%BD%BF%E7%94%A8%E5%9F%BA%E6%9C%AC%E7%BB%84%E4%BB%B6%E5%BB%BA%E7%AB%8B%E6%A8%A1%E5%9E%8B)
- [构建新模型](#%E6%9E%84%E5%BB%BA%E6%96%B0%E6%A8%A1%E5%9E%8B)
- [如何训练模型](#%E5%A6%82%E4%BD%95%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B)
- [推理流水线](#%E6%8E%A8%E7%90%86%E6%B5%81%E6%B0%B4%E7%BA%BF)
- [训练配置](#%E8%AE%AD%E7%BB%83%E9%85%8D%E7%BD%AE)
- [使用单个 GPU 进行训练](#%E4%BD%BF%E7%94%A8%E5%8D%95%E4%B8%AA-gpu-%E8%BF%9B%E8%A1%8C%E8%AE%AD%E7%BB%83)
- [使用多个 GPU 进行训练](#%E4%BD%BF%E7%94%A8%E5%A4%9A%E4%B8%AA-gpu-%E8%BF%9B%E8%A1%8C%E8%AE%AD%E7%BB%83)
- [使用多台机器进行训练](#%E4%BD%BF%E7%94%A8%E5%A4%9A%E5%8F%B0%E6%9C%BA%E5%99%A8%E8%BF%9B%E8%A1%8C%E8%AE%AD%E7%BB%83)
- [使用单台机器启动多个任务](#%E4%BD%BF%E7%94%A8%E5%8D%95%E5%8F%B0%E6%9C%BA%E5%99%A8%E5%90%AF%E5%8A%A8%E5%A4%9A%E4%B8%AA%E4%BB%BB%E5%8A%A1)
- [详细教程](#%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B)
<!-- TOC -->
## 数据集
MMAction2 建议用户将数据集根目录链接到 `$MMACTION2/data` 下。
如果用户的文件夹结构与默认结构不同,则需要在配置文件中进行对应路径的修改。
```
mmaction2
├── mmaction
├── tools
├── configs
├── data
│ ├── kinetics400
│ │ ├── rawframes_train
│ │ ├── rawframes_val
│ │ ├── kinetics_train_list.txt
│ │ ├── kinetics_val_list.txt
│ ├── ucf101
│ │ ├── rawframes_train
│ │ ├── rawframes_val
│ │ ├── ucf101_train_list.txt
│ │ ├── ucf101_val_list.txt
│ ├── ...
```
请参阅 [数据集准备](data_preparation.md) 获取数据集准备的相关信息。
对于用户自定义数据集的准备,请参阅 [教程 3:如何增加新数据集](tutorials/3_new_dataset.md)
## 使用预训练模型进行推理
MMAction2 提供了一些脚本用于测试数据集(如 Kinetics-400,Something-Something V1&V2,(Multi-)Moments in Time,等),
并提供了一些高级 API,以便更好地兼容其他项目。
MMAction2 支持仅使用 CPU 进行测试。然而,这样做的速度**非常慢**,用户应仅使用其作为无 GPU 机器上的 debug 手段。
如需使用 CPU 进行测试,用户需要首先使用命令 `export CUDA_VISIBLE_DEVICES=-1` 禁用机器上的 GPU (如有),然后使用命令 `python tools/test.py {OTHER_ARGS}` 直接调用测试脚本。
### 测试某个数据集
- [x] 支持单 GPU
- [x] 支持单节点,多 GPU
- [x] 支持多节点
用户可使用以下命令进行数据集测试
```shell
# 单 GPU 测试
python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] \
[--gpu-collect] [--tmpdir ${TMPDIR}] [--options ${OPTIONS}] [--average-clips ${AVG_TYPE}] \
[--launcher ${JOB_LAUNCHER}] [--local_rank ${LOCAL_RANK}] [--onnx] [--tensorrt]
# 多 GPU 测试
./tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] \
[--gpu-collect] [--tmpdir ${TMPDIR}] [--options ${OPTIONS}] [--average-clips ${AVG_TYPE}] \
[--launcher ${JOB_LAUNCHER}] [--local_rank ${LOCAL_RANK}]
```
可选参数:
- `RESULT_FILE`:输出结果文件名。如果没有被指定,则不会保存测试结果。
- `EVAL_METRICS`:测试指标。其可选值与对应数据集相关,如 `top_k_accuracy``mean_class_accuracy` 适用于所有动作识别数据集,`mmit_mean_average_precision` 适用于 Multi-Moments in Time 数据集,`mean_average_precision` 适用于 Multi-Moments in Time 和单类 HVU 数据集,`AR@AN` 适用于 ActivityNet 数据集等。
- `--gpu-collect`:如果被指定,动作识别结果将会通过 GPU 通信进行收集。否则,它将被存储到不同 GPU 上的 `TMPDIR` 文件夹中,并在 rank 0 的进程中被收集。
- `TMPDIR`:用于存储不同进程收集的结果文件的临时文件夹。该变量仅当 `--gpu-collect` 没有被指定时有效。
- `OPTIONS`:用于验证过程的自定义选项。其可选值与对应数据集的 `evaluate` 函数变量有关。
- `AVG_TYPE`:用于平均测试片段结果的选项。如果被设置为 `prob`,则会在平均测试片段结果之前施加 softmax 函数。否则,会直接进行平均。
- `JOB_LAUNCHER`:分布式任务初始化启动器选项。可选值有 `none``pytorch``slurm``mpi`。特别地,如果被设置为 `none`, 则会以非分布式模式进行测试。
- `LOCAL_RANK`:本地 rank 的 ID。如果没有被指定,则会被设置为 0。
- `--onnx`: 如果指定,将通过 onnx 模型推理获取预测结果,输入参数 `CHECKPOINT_FILE` 应为 onnx 模型文件。Onnx 模型文件由 `/tools/deployment/pytorch2onnx.py` 脚本导出。目前,不支持多 GPU 测试以及动态张量形状(Dynamic shape)。请注意,数据集输出与模型输入张量的形状应保持一致。同时,不建议使用测试时数据增强,如 `ThreeCrop``TenCrop``twice_sample` 等。
- `--tensorrt`: 如果指定,将通过 TensorRT 模型推理获取预测结果,输入参数 `CHECKPOINT_FILE` 应为 TensorRT 模型文件。TensorRT 模型文件由导出的 onnx 模型以及 TensorRT 官方模型转换工具生成。目前,不支持多 GPU 测试以及动态张量形状(Dynamic shape)。请注意,数据集输出与模型输入张量的形状应保持一致。同时,不建议使用测试时数据增强,如 `ThreeCrop``TenCrop``twice_sample` 等。
例子:
假定用户将下载的模型权重文件放置在 `checkpoints/` 目录下。
1. 在 Kinetics-400 数据集下测试 TSN (不存储测试结果为文件),并验证 `top-k accuracy``mean class accuracy` 指标
```shell
python tools/test.py configs/recognition/tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py \
checkpoints/SOME_CHECKPOINT.pth \
--eval top_k_accuracy mean_class_accuracy
```
2. 使用 8 块 GPU 在 Something-Something V1 下测试 TSN,并验证 `top-k accuracy` 指标
```shell
./tools/dist_test.sh configs/recognition/tsn/tsn_r50_1x1x8_50e_sthv1_rgb.py \
checkpoints/SOME_CHECKPOINT.pth \
8 --out results.pkl --eval top_k_accuracy
```
3. 在 slurm 分布式环境中测试 TSN 在 Kinetics-400 数据集下的 `top-k accuracy` 指标
```shell
python tools/test.py configs/recognition/tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py \
checkpoints/SOME_CHECKPOINT.pth \
--launcher slurm --eval top_k_accuracy
```
4. 在 Something-Something V1 下测试 onnx 格式的 TSN 模型,并验证 `top-k accuracy` 指标
```shell
python tools/test.py configs/recognition/tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py \
checkpoints/SOME_CHECKPOINT.onnx \
--eval top_k_accuracy --onnx
```
### 使用高级 API 对视频和帧文件夹进行测试
这里举例说明如何构建模型并测试给定视频
```python
import torch
from mmaction.apis import init_recognizer, inference_recognizer
config_file = 'configs/recognition/tsn/tsn_r50_video_inference_1x1x3_100e_kinetics400_rgb.py'
# 从模型库中下载检测点,并把它放到 `checkpoints/` 文件夹下
checkpoint_file = 'checkpoints/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'
# 指定设备
device = 'cuda:0' # or 'cpu'
device = torch.device(device)
# 根据配置文件和检查点来建立模型
model = init_recognizer(config_file, checkpoint_file, device=device)
# 测试单个视频并显示其结果
video = 'demo/demo.mp4'
labels = 'tools/data/kinetics/label_map_k400.txt'
results = inference_recognizer(model, video)
# 显示结果
labels = open('tools/data/kinetics/label_map_k400.txt').readlines()
labels = [x.strip() for x in labels]
results = [(labels[k[0]], k[1]) for k in results]
print(f'The top-5 labels with corresponding scores are:')
for result in results:
print(f'{result[0]}: ', result[1])
```
这里举例说明如何构建模型并测试给定帧文件夹
```python
import torch
from mmaction.apis import init_recognizer, inference_recognizer
config_file = 'configs/recognition/tsn/tsn_r50_inference_1x1x3_100e_kinetics400_rgb.py'
# 从模型库中下载检测点,并把它放到 `checkpoints/` 文件夹下
checkpoint_file = 'checkpoints/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'
# 指定设备
device = 'cuda:0' # or 'cpu'
device = torch.device(device)
# 根据配置文件和检查点来建立模型
model = init_recognizer(config_file, checkpoint_file, device=device)
# 测试单个视频的帧文件夹并显示其结果
video = 'SOME_DIR_PATH/'
labels = 'tools/data/kinetics/label_map_k400.txt'
results = inference_recognizer(model, video)
# 显示结果
labels = open('tools/data/kinetics/label_map_k400.txt').readlines()
labels = [x.strip() for x in labels]
results = [(labels[k[0]], k[1]) for k in results]
print(f'The top-5 labels with corresponding scores are:')
for result in results:
print(f'{result[0]}: ', result[1])
```
这里举例说明如何构建模型并通过 url 测试给定视频
```python
import torch
from mmaction.apis import init_recognizer, inference_recognizer
config_file = 'configs/recognition/tsn/tsn_r50_video_inference_1x1x3_100e_kinetics400_rgb.py'
# 从模型库中下载检测点,并把它放到 `checkpoints/` 文件夹下
checkpoint_file = 'checkpoints/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'
# 指定设备
device = 'cuda:0' # or 'cpu'
device = torch.device(device)
# 根据配置文件和检查点来建立模型
model = init_recognizer(config_file, checkpoint_file, device=device)
# 测试单个视频的 url 并显示其结果
video = 'https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4'
labels = 'tools/data/kinetics/label_map_k400.txt'
results = inference_recognizer(model, video)
# 显示结果
labels = open('tools/data/kinetics/label_map_k400.txt').readlines()
labels = [x.strip() for x in labels]
results = [(labels[k[0]], k[1]) for k in results]
print(f'The top-5 labels with corresponding scores are:')
for result in results:
print(f'{result[0]}: ', result[1])
```
**注意**:MMAction2 在默认提供的推理配置文件(inference configs)中定义 `data_prefix` 变量,并将其设置为 None 作为默认值。
如果 `data_prefix` 不为 None,则要获取的视频文件(或帧文件夹)的路径将为 `data_prefix/video`
在这里,`video` 是上述脚本中的同名变量。可以在 `rawframe_dataset.py` 文件和 `video_dataset.py` 文件中找到此详细信息。例如,
- 当视频(帧文件夹)路径为 `SOME_DIR_PATH/VIDEO.mp4``SOME_DIR_PATH/VIDEO_NAME/img_xxxxx.jpg`),并且配置文件中的 `data_prefix` 为 None,则 `video` 变量应为 `SOME_DIR_PATH/VIDEO.mp4``SOME_DIR_PATH/VIDEO_NAME`)。
- 当视频(帧文件夹)路径为 `SOME_DIR_PATH/VIDEO.mp4``SOME_DIR_PATH/VIDEO_NAME/img_xxxxx.jpg`),并且配置文件中的 `data_prefix``SOME_DIR_PATH`,则 `video` 变量应为 `VIDEO.mp4``VIDEO_NAME`)。
- 当帧文件夹路径为 `VIDEO_NAME/img_xxxxx.jpg`,并且配置文件中的 `data_prefix` 为 None,则 `video` 变量应为 `VIDEO_NAME`
- 当传递参数为视频 url 而非本地路径,则需使用 OpenCV 作为视频解码后端。
[demo/demo.ipynb](/demo/demo.ipynb) 中有提供相应的 notebook 演示文件。
## 如何建立模型
### 使用基本组件建立模型
MMAction2 将模型组件分为 4 种基础模型:
- 识别器(recognizer):整个识别器模型管道,通常包含一个主干网络(backbone)和分类头(cls_head)。
- 主干网络(backbone):通常为一个用于提取特征的 FCN 网络,例如 ResNet,BNInception。
- 分类头(cls_head):用于分类任务的组件,通常包括一个带有池化层的 FC 层。
- 时序检测器(localizer):用于时序检测的模型,目前有的检测器包含 BSN,BMN,SSN。
用户可参照给出的配置文件里的基础模型搭建流水线(如 `Recognizer2D`
如果想创建一些新的组件,如 [TSM: Temporal Shift Module for Efficient Video Understanding](https://arxiv.org/abs/1811.08383) 中的 temporal shift backbone 结构,则需:
1. 创建 `mmaction/models/backbones/resnet_tsm.py` 文件
```python
from ..builder import BACKBONES
from .resnet import ResNet
@BACKBONES.register_module()
class ResNetTSM(ResNet):
def __init__(self,
depth,
num_segments=8,
is_shift=True,
shift_div=8,
shift_place='blockres',
temporal_pool=False,
**kwargs):
pass
def forward(self, x):
# implementation is ignored
pass
```
2.`mmaction/models/backbones/__init__.py` 中导入模型
```python
from .resnet_tsm import ResNetTSM
```
3. 修改模型文件
```python
backbone=dict(
type='ResNet',
pretrained='torchvision://resnet50',
depth=50,
norm_eval=False)
```
修改为
```python
backbone=dict(
type='ResNetTSM',
pretrained='torchvision://resnet50',
depth=50,
norm_eval=False,
shift_div=8)
```
### 构建新模型
要编写一个新的动作识别器流水线,用户需要继承 `BaseRecognizer`,其定义了如下抽象方法
- `forward_train()`: 训练模式下的前向方法
- `forward_test()`: 测试模式下的前向方法
具体可参照 [Recognizer2D](/mmaction/models/recognizers/recognizer2d.py)[Recognizer3D](/mmaction/models/recognizers/recognizer3d.py)
## 如何训练模型
### 推理流水线
MMAction2 使用 `MMDistributedDataParallel` 进行分布式训练,使用 `MMDataParallel` 进行非分布式训练。
对于单机多卡与多台机器的情况,MMAction2 使用分布式训练。假设服务器有 8 块 GPU,则会启动 8 个进程,并且每台 GPU 对应一个进程。
每个进程拥有一个独立的模型,以及对应的数据加载器和优化器。
模型参数同步只发生于最开始。之后,每经过一次前向与后向计算,所有 GPU 中梯度都执行一次 allreduce 操作,而后优化器将更新模型参数。
由于梯度执行了 allreduce 操作,因此不同 GPU 中模型参数将保持一致。
### 训练配置
所有的输出(日志文件和模型权重文件)会被将保存到工作目录下。工作目录通过配置文件中的参数 `work_dir` 指定。
默认情况下,MMAction2 在每个周期后会在验证集上评估模型,可以通过在训练配置中修改 `interval` 参数来更改评估间隔
```python
evaluation = dict(interval=5) # 每 5 个周期进行一次模型评估
```
根据 [Linear Scaling Rule](https://arxiv.org/abs/1706.02677),当 GPU 数量或每个 GPU 上的视频批大小改变时,用户可根据批大小按比例地调整学习率,如,当 4 GPUs x 2 video/gpu 时,lr=0.01;当 16 GPUs x 4 video/gpu 时,lr=0.08。
MMAction2 支持仅使用 CPU 进行训练。然而,这样做的速度**非常慢**,用户应仅使用其作为无 GPU 机器上的 debug 手段。
如需使用 CPU 进行训练,用户需要首先使用命令 `export CUDA_VISIBLE_DEVICES=-1` 禁用机器上的 GPU (如有),然后使用命令 `python tools/train.py {OTHER_ARGS}` 直接调用训练脚本。
### 使用单个 GPU 进行训练
```shell
python tools/train.py ${CONFIG_FILE} [optional arguments]
```
如果用户想在命令中指定工作目录,则需要增加参数 `--work-dir ${YOUR_WORK_DIR}`
### 使用多个 GPU 进行训练
```shell
./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments]
```
可选参数为:
- `--validate` (**强烈建议**):在训练期间每 k 个周期进行一次验证(默认值为 5,可通过修改每个配置文件中的 `evaluation` 字典变量的 `interval` 值进行改变)。
- `--test-last`:在训练结束后使用最后一个检查点的参数进行测试,将测试结果存储在 `${WORK_DIR}/last_pred.pkl` 中。
- `--test-best`:在训练结束后使用效果最好的检查点的参数进行测试,将测试结果存储在 `${WORK_DIR}/best_pred.pkl` 中。
- `--work-dir ${WORK_DIR}`:覆盖配置文件中指定的工作目录。
- `--resume-from ${CHECKPOINT_FILE}`:从以前的模型权重文件恢复训练。
- `--gpus ${GPU_NUM}`:使用的 GPU 数量,仅适用于非分布式训练。
- `--gpu-ids ${GPU_IDS}`:使用的 GPU ID,仅适用于非分布式训练。
- `--seed ${SEED}`:设置 python,numpy 和 pytorch 里的种子 ID,已用于生成随机数。
- `--deterministic`:如果被指定,程序将设置 CUDNN 后端的确定化选项。
- `JOB_LAUNCHER`:分布式任务初始化启动器选项。可选值有 `none``pytorch``slurm``mpi`。特别地,如果被设置为 `none`, 则会以非分布式模式进行测试。
- `LOCAL_RANK`:本地 rank 的 ID。如果没有被指定,则会被设置为 0。
`resume-from``load-from` 的不同点:
`resume-from` 加载模型参数和优化器状态,并且保留检查点所在的周期数,常被用于恢复意外被中断的训练。
`load-from` 只加载模型参数,但周期数从 0 开始计数,常被用于微调模型。
这里提供一个使用 8 块 GPU 加载 TSN 模型权重文件的例子。
```shell
./tools/dist_train.sh configs/recognition/tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py 8 --resume-from work_dirs/tsn_r50_1x1x3_100e_kinetics400_rgb/latest.pth
```
### 使用多台机器进行训练
如果用户在 [slurm](https://slurm.schedmd.com/) 集群上运行 MMAction2,可使用 `slurm_train.sh` 脚本。(该脚本也支持单台机器上进行训练)
```shell
[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} [--work-dir ${WORK_DIR}]
```
这里给出一个在 slurm 集群上的 dev 分区使用 16 块 GPU 训练 TSN 的例子。(使用 `GPUS_PER_NODE=8` 参数来指定一个有 8 块 GPUS 的 slurm 集群节点)
```shell
GPUS=16 ./tools/slurm_train.sh dev tsn_r50_k400 configs/recognition/tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py --work-dir work_dirs/tsn_r50_1x1x3_100e_kinetics400_rgb
```
用户可以查看 [slurm_train.sh](/tools/slurm_train.sh) 文件来检查完整的参数和环境变量。
如果您想使用由 ethernet 连接起来的多台机器, 您可以使用以下命令:
在第一台机器上:
```shell
NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
```
在第二台机器上:
```shell
NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
```
但是,如果您不使用高速网路连接这几台机器的话,训练将会非常慢。
### 使用单台机器启动多个任务
如果用使用单台机器启动多个任务,如在有 8 块 GPU 的单台机器上启动 2 个需要 4 块 GPU 的训练任务,则需要为每个任务指定不同端口,以避免通信冲突。
如果用户使用 `dist_train.sh` 脚本启动训练任务,则可以通过以下命令指定端口
```shell
CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4
CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4
```
如果用户在 slurm 集群下启动多个训练任务,则需要修改配置文件(通常是配置文件的倒数第 6 行)中的 `dist_params` 变量,以设置不同的通信端口。
`config1.py` 中,
```python
dist_params = dict(backend='nccl', port=29500)
```
`config2.py` 中,
```python
dist_params = dict(backend='nccl', port=29501)
```
之后便可启动两个任务,分别对应 `config1.py``config2.py`
```shell
CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py [--work-dir ${WORK_DIR}]
CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py [--work-dir ${WORK_DIR}]
```
## 详细教程
目前, MMAction2 提供以下几种更详细的教程:
- [如何编写配置文件](tutorials/1_config.md)
- [如何微调模型](tutorials/2_finetune.md)
- [如何增加新数据集](tutorials/3_new_dataset.md)
- [如何设计数据处理流程](tutorials/4_data_pipeline.md)
- [如何增加新模块](tutorials/5_new_modules.md)
- [如何导出模型为 onnx 格式](tutorials/6_export_model.md)
- [如何自定义模型运行参数](tutorials/7_customize_runtime.md)
欢迎来到 MMAction2 的中文文档!
=====================================
您可以在页面左下角切换中英文文档。
You can change the documentation language at the lower-left corner of the page.
.. toctree::
:maxdepth: 2
install.md
getting_started.md
demo.md
benchmark.md
.. toctree::
:maxdepth: 2
:caption: 数据集
datasets.md
data_preparation.md
supported_datasets.md
.. toctree::
:maxdepth: 2
:caption: 模型库
modelzoo.md
recognition_models.md
localization_models.md
detection_models.md
skeleton_models.md
.. toctree::
:maxdepth: 2
:caption: 教程
tutorials/1_config.md
tutorials/2_finetune.md
tutorials/3_new_dataset.md
tutorials/4_data_pipeline.md
tutorials/5_new_modules.md
tutorials/6_export_model.md
tutorials/7_customize_runtime.md
.. toctree::
:maxdepth: 2
:caption: 实用工具和脚本
useful_tools.md
.. toctree::
:maxdepth: 2
:caption: 记录
changelog.md
faq.md
.. toctree::
:caption: API 参考文档
api.rst
.. toctree::
:caption: 语言切换
switch_language.md
索引和表格
==================
* :ref:`genindex`
* :ref:`search`
# 安装
本文档提供了安装 MMAction2 的相关步骤。
<!-- TOC -->
- [安装](#%E5%AE%89%E8%A3%85)
- [安装依赖包](#%E5%AE%89%E8%A3%85%E4%BE%9D%E8%B5%96%E5%8C%85)
- [准备环境](#%E5%87%86%E5%A4%87%E7%8E%AF%E5%A2%83)
- [MMAction2 的安装步骤](#mmaction2-%E7%9A%84%E5%AE%89%E8%A3%85%E6%AD%A5%E9%AA%A4)
- [CPU 环境下的安装步骤](#cpu-%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E5%AE%89%E8%A3%85%E6%AD%A5%E9%AA%A4)
- [利用 Docker 镜像安装 MMAction2](#%E5%88%A9%E7%94%A8-docker-%E9%95%9C%E5%83%8F%E5%AE%89%E8%A3%85-mmaction2)
- [源码安装 MMAction2](#%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85-mmaction2)
- [在多个 MMAction2 版本下进行开发](#%E5%9C%A8%E5%A4%9A%E4%B8%AA-mmaction2-%E7%89%88%E6%9C%AC%E4%B8%8B%E8%BF%9B%E8%A1%8C%E5%BC%80%E5%8F%91)
- [安装验证](#%E5%AE%89%E8%A3%85%E9%AA%8C%E8%AF%81)
<!-- TOC -->
## 安装依赖包
- Linux (Windows 系统暂未有官方支持)
- Python 3.6+
- PyTorch 1.3+
- CUDA 9.2+ (如果要从源码对 PyTorch 进行编译, CUDA 9.0 版本同样可以兼容)
- GCC 5+
- [mmcv](https://github.com/open-mmlab/mmcv) 1.1.1+
- Numpy
- ffmpeg (4.2 版本最佳)
- [decord](https://github.com/dmlc/decord) (可选项, 0.4.1+):使用 `pip install decord==0.4.1` 命令安装其 CPU 版本,GPU 版本需从源码进行编译。
- [PyAV](https://github.com/mikeboers/PyAV) (可选项)`conda install av -c conda-forge -y`
- [PyTurboJPEG](https://github.com/lilohuang/PyTurboJPEG) (可选项)`pip install PyTurboJPEG`
- [denseflow](https://github.com/open-mmlab/denseflow) (可选项):可参考 [这里](https://github.com/innerlee/setup) 获取简便安装步骤。
- [moviepy](https://zulko.github.io/moviepy/) (可选项)`pip install moviepy`. 官方安装步骤可参考 [这里](https://zulko.github.io/moviepy/install.html)**特别地**,如果安装过程碰到 [这个问题](https://github.com/Zulko/moviepy/issues/693),可参考:
1. 对于 Windows 用户, [ImageMagick](https://www.imagemagick.org/script/index.php) 将不会被 MoviePy 自动检测到,用户需要对 `moviepy/config_defaults.py` 文件进行修改,以提供 ImageMagick 的二进制文件(即,`magick`)的路径,如 `IMAGEMAGICK_BINARY = "C:\\Program Files\\ImageMagick_VERSION\\magick.exe"`
2. 对于 Linux 用户, 如果 [ImageMagick](https://www.imagemagick.org/script/index.php) 没有被 `moviepy` 检测到,用于需要对 `/etc/ImageMagick-6/policy.xml` 文件进行修改,把文件中的 `<policy domain="path" rights="none" pattern="@*" />` 代码行修改为 `<!-- <policy domain="path" rights="none" pattern="@*" /> -->`
- [Pillow-SIMD](https://docs.fast.ai/performance.html#pillow-simd) (可选项):可使用如下脚本进行安装:
```shell
conda uninstall -y --force pillow pil jpeg libtiff libjpeg-turbo
pip uninstall -y pillow pil jpeg libtiff libjpeg-turbo
conda install -yc conda-forge libjpeg-turbo
CFLAGS="${CFLAGS} -mavx2" pip install --upgrade --no-cache-dir --force-reinstall --no-binary :all: --compile pillow-simd
conda install -y jpeg libtiff
```
**注意**:用户需要首先运行 `pip uninstall mmcv` 命令,以确保 mmcv 被成功安装。
如果 mmcv 和 mmcv-full 同时被安装, 会报 `ModuleNotFoundError` 的错误。
## 准备环境
a. 创建并激活 conda 虚拟环境,如:
```shell
conda create -n open-mmlab python=3.7 -y
conda activate open-mmlab
```
b. 根据 [官方文档](https://pytorch.org/) 进行 PyTorch 和 torchvision 的安装,如:
```shell
conda install pytorch torchvision -c pytorch
```
**注**:确保 CUDA 的编译版本和 CUDA 的运行版本相匹配。
用户可以参照 [PyTorch 官网](https://pytorch.org/) 对预编译包所支持的 CUDA 版本进行核对。
`例 1`:如果用户的 `/usr/local/cuda` 文件夹下已安装 CUDA 10.1 版本,并且想要安装 PyTorch 1.5 版本,
则需要安装 CUDA 10.1 下预编译的 PyTorch。
```shell
conda install pytorch cudatoolkit=10.1 torchvision -c pytorch
```
`例 2`:如果用户的 `/usr/local/cuda` 文件夹下已安装 CUDA 9.2 版本,并且想要安装 PyTorch 1.3.1 版本,
则需要安装 CUDA 9.2 下预编译的 PyTorch。
```shell
conda install pytorch=1.3.1 cudatoolkit=9.2 torchvision=0.4.2 -c pytorch
```
如果 PyTorch 是由源码进行编译安装(而非直接下载预编译好的安装包),则可以使用更多的 CUDA 版本(如 9.0 版本)。
## MMAction2 的安装步骤
这里推荐用户使用 [MIM](https://github.com/open-mmlab/mim) 安装 MMAction2。
```shell
pip install git+https://github.com/open-mmlab/mim.git
mim install mmaction2 -f https://github.com/open-mmlab/mmaction2.git
```
MIM 可以自动安装 OpenMMLab 项目及其依赖。
或者,用户也可以通过以下步骤手动安装 MMAction2。
a. 安装 mmcv-full,我们推荐您安装以下预构建包:
```shell
# pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/{cu_version}/{torch_version}/index.html
pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.10.0/index.html
```
PyTorch 在 1.x.0 和 1.x.1 之间通常是兼容的,故 mmcv-full 只提供 1.x.0 的编译包。如果你的 PyTorch 版本是 1.x.1,你可以放心地安装在 1.x.0 版本编译的 mmcv-full。
```
# 我们可以忽略 PyTorch 的小版本号
pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.10/index.html
```
可查阅 [这里](https://github.com/open-mmlab/mmcv#installation) 以参考不同版本的 MMCV 所兼容的 PyTorch 和 CUDA 版本。
另外,用户也可以通过使用以下命令从源码进行编译:
```shell
git clone https://github.com/open-mmlab/mmcv.git
cd mmcv
MMCV_WITH_OPS=1 pip install -e . # mmcv-full 包含一些 cuda 算子,执行该步骤会安装 mmcv-full(而非 mmcv)
# 或者使用 pip install -e . # 这个命令安装的 mmcv 将不包含 cuda ops,通常适配 CPU(无 GPU)环境
cd ..
```
或者直接运行脚本:
```shell
pip install mmcv-full
```
**注意**:如果 mmcv 已经被安装,用户需要使用 `pip uninstall mmcv` 命令进行卸载。如果 mmcv 和 mmcv-full 同时被安装, 会报 `ModuleNotFoundError` 的错误。
b. 克隆 MMAction2 库。
```shell
git clone https://github.com/open-mmlab/mmaction2.git
cd mmaction2
```
c. 安装依赖包和 MMAction2。
```shell
pip install -r requirements/build.txt
pip install -v -e . # or "python setup.py develop"
```
如果是在 macOS 环境安装 MMAction2,则需使用如下命令:
```shell
CC=clang CXX=clang++ CFLAGS='-stdlib=libc++' pip install -e .
```
d. 安装 mmdetection 以支持时空检测任务。
如果用户不想做时空检测相关任务,这部分步骤可以选择跳过。
可参考 [这里](https://github.com/open-mmlab/mmdetection#installation) 进行 mmdetection 的安装。
注意:
1. 在步骤 b 中,git commit 的 id 将会被写到版本号中,如 0.6.0+2e7045c。这个版本号也会被保存到训练好的模型中。
这里推荐用户每次在步骤 b 中对本地代码和 github 上的源码进行同步。如果 C++/CUDA 代码被修改,就必须进行这一步骤。
2. 根据上述步骤,MMAction2 就会以 `dev` 模式被安装,任何本地的代码修改都会立刻生效,不需要再重新安装一遍(除非用户提交了 commits,并且想更新版本号)。
3. 如果用户想使用 `opencv-python-headless` 而不是 `opencv-python`,可再安装 MMCV 前安装 `opencv-python-headless`
4. 如果用户想使用 `PyAV`,可以通过 `conda install av -c conda-forge -y` 进行安装。
5. 一些依赖包是可选的。运行 `python setup.py develop` 将只会安装运行代码所需的最小要求依赖包。
要想使用一些可选的依赖包,如 `decord`,用户需要通过 `pip install -r requirements/optional.txt` 进行安装,
或者通过调用 `pip`(如 `pip install -v -e .[optional]`,这里的 `[optional]` 可替换为 `all``tests``build``optional`) 指定安装对应的依赖包,如 `pip install -v -e .[tests,build]`
## CPU 环境下的安装步骤
MMAction2 可以在只有 CPU 的环境下安装(即无法使用 GPU 的环境)。
在 CPU 模式下,用户可以运行 `demo/demo.py` 的代码。
## 利用 Docker 镜像安装 MMAction2
MMAction2 提供一个 [Dockerfile](/docker/Dockerfile) 用户创建 docker 镜像。
```shell
# 创建拥有 PyTorch 1.6.0, CUDA 10.1, CUDNN 7 配置的 docker 镜像.
docker build -f ./docker/Dockerfile --rm -t mmaction2 .
```
**注意**:用户需要确保已经安装了 [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker)
运行以下命令:
```shell
docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmaction2/data mmaction2
```
## 源码安装 MMAction2
这里提供了 conda 下安装 MMAction2 并链接数据集路径的完整脚本(假设 Kinetics-400 数据的路径在 $KINETICS400_ROOT)。
```shell
conda create -n open-mmlab python=3.7 -y
conda activate open-mmlab
# 安装最新的,使用默认版本的 CUDA 版本(一般为最新版本)预编译的 PyTorch 包
conda install -c pytorch pytorch torchvision -y
# 安装最新版本的 mmcv 或 mmcv-full,这里以 mmcv 为例
pip install mmcv
# 安装 mmaction2
git clone https://github.com/open-mmlab/mmaction2.git
cd mmaction2
pip install -r requirements/build.txt
python setup.py develop
mkdir data
ln -s $KINETICS400_ROOT data
```
## 在多个 MMAction2 版本下进行开发
MMAction2 的训练和测试脚本已经修改了 `PYTHONPATH` 变量,以确保其能够运行当前目录下的 MMAction2。
如果想要运行环境下默认的 MMAction2,用户需要在训练和测试脚本中去除这一行:
```shell
PYTHONPATH="$(dirname $0)/..":$PYTHONPATH
```
## 安装验证
为了验证 MMAction2 和所需的依赖包是否已经安装成功,
用户可以运行以下的 python 代码,以测试其是否能成功地初始化动作识别器,并进行演示视频的推理:
```python
import torch
from mmaction.apis import init_recognizer, inference_recognizer
config_file = 'configs/recognition/tsn/tsn_r50_video_inference_1x1x3_100e_kinetics400_rgb.py'
device = 'cuda:0' # 或 'cpu'
device = torch.device(device)
model = init_recognizer(config_file, device=device)
# 进行演示视频的推理
inference_recognizer(model, 'demo/demo.mp4')
```
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
#!/usr/bin/env bash
# gather models
cat ../configs/localization/*/README_zh-CN.md | sed "s/md#测/html#测/g" | sed "s/md#训/html#训/g" | sed "s/#/#&/" | sed '1i\# 时序动作检测模型' | sed 's/](\/docs_zh_CN\//](/g' | sed 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' | sed "s/getting_started.html##/getting_started.html#/g" > localization_models.md
cat ../configs/recognition/*/README_zh-CN.md | sed "s/md#测/html#t测/g" | sed "s/md#训/html#训/g" | sed "s/#/#&/" | sed '1i\# 动作识别模型' | sed 's/](\/docs_zh_CN\//](/g' | sed 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g'| sed "s/getting_started.html##/getting_started.html#/g" > recognition_models.md
cat ../configs/recognition_audio/*/README_zh-CN.md | sed "s/md#测/html#测/g" | sed "s/md#训/html#训/g" | sed "s/#/#&/" | sed 's/](\/docs_zh_CN\//](/g' | sed 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g'| sed "s/getting_started.html##/getting_started.html#/g" >> recognition_models.md
cat ../configs/detection/*/README_zh-CN.md | sed "s/md#测/html#测/g" | sed "s/md#训/html#训/g" | sed "s/#/#&/" | sed '1i\# 时空动作检测模型' | sed 's/](\/docs_zh_CN\//](/g' | sed 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g'| sed "s/getting_started.html##/getting_started.html#/g" > detection_models.md
cat ../configs/skeleton/*/README_zh-CN.md | sed "s/md#测/html#测/g" | sed "s/md#训/html#训/g" | sed "s/#/#&/" | sed '1i\# 骨骼动作识别模型' | sed 's/](\/docs_zh_CN\//](/g' | sed 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g'| sed "s/getting_started.html##/getting_started.html#/g" > skeleton_models.md
# gather datasets
cat ../tools/data/*/README_zh-CN.md | sed 's/# 准备/# /g' | sed 's/#/#&/' > prepare_data.md
sed -i 's/(\/tools\/data\/activitynet\/README_zh-CN.md/(#activitynet/g' supported_datasets.md
sed -i 's/(\/tools\/data\/kinetics\/README_zh-CN.md/(#kinetics-400-600-700/g' supported_datasets.md
sed -i 's/(\/tools\/data\/mit\/README_zh-CN.md/(#moments-in-time/g' supported_datasets.md
sed -i 's/(\/tools\/data\/mmit\/README_zh-CN.md/(#multi-moments-in-time/g' supported_datasets.md
sed -i 's/(\/tools\/data\/sthv1\/README_zh-CN.md/(#something-something-v1/g' supported_datasets.md
sed -i 's/(\/tools\/data\/sthv2\/README_zh-CN.md/(#something-something-v2/g' supported_datasets.md
sed -i 's/(\/tools\/data\/thumos14\/README_zh-CN.md/(#thumos-14/g' supported_datasets.md
sed -i 's/(\/tools\/data\/ucf101\/README_zh-CN.md/(#ucf-101/g' supported_datasets.md
sed -i 's/(\/tools\/data\/ucf101_24\/README_zh-CN.md/(#ucf101-24/g' supported_datasets.md
sed -i 's/(\/tools\/data\/jhmdb\/README_zh-CN.md/(#jhmdb/g' supported_datasets.md
sed -i 's/(\/tools\/data\/hvu\/README_zh-CN.md/(#hvu/g' supported_datasets.md
sed -i 's/(\/tools\/data\/hmdb51\/README_zh-CN.md/(#hmdb51/g' supported_datasets.md
sed -i 's/(\/tools\/data\/jester\/README_zh-CN.md/(#jester/g' supported_datasets.md
sed -i 's/(\/tools\/data\/ava\/README_zh-CN.md/(#ava/g' supported_datasets.md
sed -i 's/(\/tools\/data\/gym\/README_zh-CN.md/(#gym/g' supported_datasets.md
sed -i 's/(\/tools\/data\/omnisource\/README_zh-CN.md/(#omnisource/g' supported_datasets.md
sed -i 's/(\/tools\/data\/diving48\/README_zh-CN.md/(#diving48/g' supported_datasets.md
sed -i 's/(\/tools\/data\/skeleton\/README_zh-CN.md/(#skeleton/g' supported_datasets.md
cat prepare_data.md >> supported_datasets.md
sed -i 's/](\/docs_zh_CN\//](/g' supported_datasets.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' supported_datasets.md
sed -i "s/md###t/html#t/g" demo.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' demo.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' benchmark.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' getting_started.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' install.md
sed -i 's/](\/docs_zh_CN\//](/g' ./tutorials/*.md
sed -i 's=](/=](https://github.com/open-mmlab/mmaction2/tree/master/=g' ./tutorials/*.md
#!/usr/bin/env python
# Copyright (c) OpenMMLab. All rights reserved.
import functools as func
import glob
import re
from os.path import basename, splitext
import numpy as np
import titlecase
def anchor(name):
return re.sub(r'-+', '-', re.sub(r'[^a-zA-Z0-9]', '-',
name.strip().lower())).strip('-')
# Count algorithms
files = sorted(glob.glob('*_models.md'))
stats = []
for f in files:
with open(f, 'r') as content_file:
content = content_file.read()
# title
title = content.split('\n')[0].replace('#', '')
# skip IMAGE and ABSTRACT tags
content = [
x for x in content.split('\n')
if 'IMAGE' not in x and 'ABSTRACT' not in x
]
content = '\n'.join(content)
# count papers
papers = set(
(papertype, titlecase.titlecase(paper.lower().strip()))
for (papertype, paper) in re.findall(
r'<!--\s*\[([A-Z]*?)\]\s*-->\s*\n.*?\btitle\s*=\s*{(.*?)}',
content, re.DOTALL))
# paper links
revcontent = '\n'.join(list(reversed(content.splitlines())))
paperlinks = {}
for _, p in papers:
print(p)
q = p.replace('\\', '\\\\').replace('?', '\\?')
paperlinks[p] = ' '.join(
(f'[->]({splitext(basename(f))[0]}.html#{anchor(paperlink)})'
for paperlink in re.findall(
rf'\btitle\s*=\s*{{\s*{q}\s*}}.*?\n## (.*?)\s*[,;]?\s*\n',
revcontent, re.DOTALL | re.IGNORECASE)))
print(' ', paperlinks[p])
paperlist = '\n'.join(
sorted(f' - [{t}] {x} ({paperlinks[x]})' for t, x in papers))
# count configs
configs = set(x.lower().strip()
for x in re.findall(r'https.*configs/.*\.py', content))
# count ckpts
ckpts = set(x.lower().strip()
for x in re.findall(r'https://download.*\.pth', content)
if 'mmaction' in x)
statsmsg = f"""
## [{title}]({f})
* 模型权重文件数量: {len(ckpts)}
* 配置文件数量: {len(configs)}
* 论文数量: {len(papers)}
{paperlist}
"""
stats.append((papers, configs, ckpts, statsmsg))
allpapers = func.reduce(lambda a, b: a.union(b), [p for p, _, _, _ in stats])
allconfigs = func.reduce(lambda a, b: a.union(b), [c for _, c, _, _ in stats])
allckpts = func.reduce(lambda a, b: a.union(b), [c for _, _, c, _ in stats])
msglist = '\n'.join(x for _, _, _, x in stats)
papertypes, papercounts = np.unique([t for t, _ in allpapers],
return_counts=True)
countstr = '\n'.join(
[f' - {t}: {c}' for t, c in zip(papertypes, papercounts)])
modelzoo = f"""
# 总览
* 模型权重文件数量: {len(allckpts)}
* 配置文件数量: {len(allconfigs)}
* 论文数量: {len(allpapers)}
{countstr}
有关受支持的数据集,可参见 [数据集总览](datasets.md)。
{msglist}
"""
with open('modelzoo.md', 'w') as f:
f.write(modelzoo)
# Count datasets
files = ['supported_datasets.md']
datastats = []
for f in files:
with open(f, 'r') as content_file:
content = content_file.read()
# title
title = content.split('\n')[0].replace('#', '')
# count papers
papers = set(
(papertype, titlecase.titlecase(paper.lower().strip()))
for (papertype, paper) in re.findall(
r'<!--\s*\[([A-Z]*?)\]\s*-->\s*\n.*?\btitle\s*=\s*{(.*?)}',
content, re.DOTALL))
# paper links
revcontent = '\n'.join(list(reversed(content.splitlines())))
paperlinks = {}
for _, p in papers:
print(p)
q = p.replace('\\', '\\\\').replace('?', '\\?')
paperlinks[p] = ', '.join(
(f'[{p.strip()} ->]({splitext(basename(f))[0]}.html#{anchor(p)})'
for p in re.findall(
rf'\btitle\s*=\s*{{\s*{q}\s*}}.*?\n## (.*?)\s*[,;]?\s*\n',
revcontent, re.DOTALL | re.IGNORECASE)))
print(' ', paperlinks[p])
paperlist = '\n'.join(
sorted(f' - [{t}] {x} ({paperlinks[x]})' for t, x in papers))
statsmsg = f"""
## [{title}]({f})
* 论文数量: {len(papers)}
{paperlist}
"""
datastats.append((papers, configs, ckpts, statsmsg))
alldatapapers = func.reduce(lambda a, b: a.union(b),
[p for p, _, _, _ in datastats])
# Summarize
msglist = '\n'.join(x for _, _, _, x in stats)
datamsglist = '\n'.join(x for _, _, _, x in datastats)
papertypes, papercounts = np.unique([t for t, _ in alldatapapers],
return_counts=True)
countstr = '\n'.join(
[f' - {t}: {c}' for t, c in zip(papertypes, papercounts)])
modelzoo = f"""
# 总览
* 论文数量: {len(alldatapapers)}
{countstr}
有关受支持的视频理解算法,可参见 [模型总览](modelzoo.md)。
{datamsglist}
"""
with open('datasets.md', 'w') as f:
f.write(modelzoo)
# 支持的数据集
- 支持的动作识别数据集:
- [UCF101](/tools/data/ucf101/README_zh-CN.md) \[ [主页](https://www.crcv.ucf.edu/research/data-sets/ucf101/) \].
- [HMDB51](/tools/data/hmdb51/README_zh-CN.md) \[ [主页](https://serre-lab.clps.brown.edu/resource/hmdb-a-large-human-motion-database/) \].
- [Kinetics-\[400/600/700\]](/tools/data/kinetics/README_zh-CN.md) \[ [主页](https://deepmind.com/research/open-source/kinetics) \]
- [Something-Something V1](/tools/data/sthv1/README_zh-CN.md) \[ [主页](https://20bn.com/datasets/something-something/v1) \]
- [Something-Something V2](/tools/data/sthv2/README_zh-CN.md) \[ [主页](https://20bn.com/datasets/something-something) \]
- [Moments in Time](/tools/data/mit/README_zh-CN.md) \[ [主页](http://moments.csail.mit.edu/) \]
- [Multi-Moments in Time](/tools/data/mmit/README_zh-CN.md) \[ [主页](http://moments.csail.mit.edu/challenge_iccv_2019.html) \]
- [HVU](/tools/data/hvu/README_zh-CN.md) \[ [主页](https://github.com/holistic-video-understanding/HVU-Dataset) \]
- [Jester](/tools/data/jester/README_zh-CN.md) \[ [主页](https://20bn.com/datasets/jester/v1) \]
- [GYM](/tools/data/gym/README_zh-CN.md) \[ [主页](https://sdolivia.github.io/FineGym/) \]
- [ActivityNet](/tools/data/activitynet/README_zh-CN.md) \[ [主页](http://activity-net.org/) \]
- 支持的时序动作检测数据集:
- [ActivityNet](/tools/data/activitynet/README_zh-CN.md) \[ [主页](http://activity-net.org/) \]
- [THUMOS14](/tools/data/thumos14/README_zh-CN.md) \[ [主页](https://www.crcv.ucf.edu/THUMOS14/download.html) \]
- 支持的时空动作检测数据集:
- [AVA](/tools/data/ava/README_zh-CN.md) \[ [主页](https://research.google.com/ava/index.html) \]
- [UCF101-24](/tools/data/ucf101_24/README_zh-CN.md) \[ [主页](http://www.thumos.info/download.html) \]
- [JHMDB](/tools/data/jhmdb/README_zh-CN.md) \[ [主页](http://jhmdb.is.tue.mpg.de/) \]
- 基于人体骨架的动作识别数据集:
- [PoseC3D Skeleton Dataset](/tools/data/skeleton/README.md) \[ [主页](https://kennymckormick.github.io/posec3d/) \]
MMAction2 目前支持的数据集如上所列。
MMAction2 在 `$MMACTION2/tools/data/` 路径下提供数据集准备脚本。
每个数据集的详细准备教程也在 [Readthedocs](https://mmaction2.readthedocs.io/zh_CN/latest/supported_datasets.html) 中给出。
## <a href='https://mmaction2.readthedocs.io/en/latest/'>English</a>
## <a href='https://mmaction2.readthedocs.io/zh_CN/latest/'>简体中文</a>
# 教程 1:如何编写配置文件
MMAction2 使用 python 文件作为配置文件。其配置文件系统的设计将模块化与继承整合进来,方便用户进行各种实验。
MMAction2 提供的所有配置文件都放置在 `$MMAction2/configs` 文件夹下,用户可以通过运行命令
`python tools/analysis/print_config.py /PATH/TO/CONFIG` 来查看完整的配置信息,从而方便检查所对应的配置文件。
<!-- TOC -->
- [通过命令行参数修改配置信息](#%E9%80%9A%E8%BF%87%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF)
- [配置文件结构](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84)
- [配置文件命名规则](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%91%BD%E5%90%8D%E8%A7%84%E5%88%99)
- [时序动作检测的配置文件系统](#%E6%97%B6%E5%BA%8F%E5%8A%A8%E4%BD%9C%E6%A3%80%E6%B5%8B%E7%9A%84%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)
- [动作识别的配置文件系统](#%E5%8A%A8%E4%BD%9C%E8%AF%86%E5%88%AB%E7%9A%84%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)
- [时空动作检测的配置文件系统](#%E6%97%B6%E7%A9%BA%E5%8A%A8%E4%BD%9C%E6%A3%80%E6%B5%8B%E7%9A%84%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)
- [常见问题](#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
- [配置文件中的中间变量](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E4%B8%AD%E9%97%B4%E5%8F%98%E9%87%8F)
<!-- TOC -->
## 通过命令行参数修改配置信息
当用户使用脚本 "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)]"`。注意这里的引号 " 对于列表/元组数据类型的修改是必要的,
并且 **不允许** 引号内所指定的值的书写存在空格。
## 配置文件结构
`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](https://mmcv.readthedocs.io/en/latest/understand_mmcv/config.html)
## 配置文件命名规则
MMAction2 按照以下风格进行配置文件命名,代码库的贡献者需要遵循相同的命名规则。
```
{model}_[model setting]_{backbone}_[misc]_{data setting}_[gpu x batch_per_gpu]_{schedule}_{dataset}_{modality}
```
其中,`{xxx}` 表示必要的命名域,`[yyy]` 表示可选的命名域。
- `{model}`:模型类型,如 `tsn``i3d` 等。
- `[model setting]`:一些模型上的特殊设置。
- `{backbone}`:主干网络类型,如 `r50`(ResNet-50)等。
- `[misc]`:模型的额外设置或插件,如 `dense``320p``video`等。
- `{data setting}`:采帧数据格式,形如 `{clip_len}x{frame_interval}x{num_clips}`
- `[gpu x batch_per_gpu]`:GPU 数量以及每个 GPU 上的采样。
- `{schedule}`:训练策略设置,如 `20e` 表示 20 个周期(epoch)。
- `{dataset}`:数据集名,如 `kinetics400``mmit`等。
- `{modality}`:帧的模态,如 `rgb`, `flow`等。
### 时序动作检测的配置文件系统
MMAction2 将模块化设计整合到配置文件系统中,以便于执行各种不同的实验。
- 以 BMN 为例
为了帮助用户理解 MMAction2 的配置文件结构,以及时序动作检测系统中的一些模块,这里以 BMN 为例,给出其配置文件的注释。
对于每个模块的详细用法以及对应参数的选择,请参照 [API 文档](https://mmaction2.readthedocs.io/en/latest/api.html)
```python
# 模型设置
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') # 输出文件格式
```
### 动作识别的配置文件系统
MMAction2 将模块化设计整合到配置文件系统中,以便执行各类不同实验。
- 以 TSN 为例
为了帮助用户理解 MMAction2 的配置文件结构,以及动作识别系统中的一些模块,这里以 TSN 为例,给出其配置文件的注释。
对于每个模块的详细用法以及对应参数的选择,请参照 [API 文档](https://mmaction2.readthedocs.io/en/latest/api.html)
```python
# 模型设置
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 的执行流只执行一次
```
### 时空动作检测的配置文件系统
MMAction2 将模块化设计整合到配置文件系统中,以便于执行各种不同的实验。
- 以 FastRCNN 为例
为了帮助用户理解 MMAction2 的完整配置文件结构,以及时空检测系统中的一些模块,这里以 FastRCNN 为例,给出其配置文件的注释。
对于每个模块的详细用法以及对应参数的选择,请参照 [API 文档](https://mmaction2.readthedocs.io/en/latest/api.html)
```python
# 模型设置
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 # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行
```
## 常见问题
### 配置文件中的中间变量
配置文件中会用到一些中间变量,如 `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_test``data_root`/`data_root_val` 为数据处理流程提供一些基本信息。
此外,使用 `img_norm_cfg` 作为中间变量,构建一些数组增强组件。
```python
...
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:如何微调模型
本教程介绍如何使用预训练模型在其他数据集上进行微调。
<!-- TOC -->
- [概要](#%E6%A6%82%E8%A6%81)
- [修改 Head](#%E4%BF%AE%E6%94%B9-Head)
- [修改数据集](#%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E9%9B%86)
- [修改训练策略](#%E4%BF%AE%E6%94%B9%E8%AE%AD%E7%BB%83%E7%AD%96%E7%95%A5)
- [使用预训练模型](#%E4%BD%BF%E7%94%A8%E9%A2%84%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B)
<!-- TOC -->
## 概要
对新数据集上的模型进行微调需要进行两个步骤:
1. 增加对新数据集的支持。详情请见 [教程 3:如何增加新数据集](3_new_dataset.md)
2. 修改配置文件。这部分将在本教程中做具体讨论。
例如,如果用户想要微调 Kinetics-400 数据集的预训练模型到另一个数据集上,如 UCF101,则需要注意 [配置文件](1_config.md) 中 Head、数据集、训练策略、预训练模型四个部分,下面分别介绍。
## 修改 Head
`cls_head` 中的 `num_classes` 参数需改为新数据集中的类别数。
预训练模型中,除了最后一层外的权重都会被重新利用,因此这个改动是安全的。
例如,UCF101 拥有 101 类行为,因此需要把 400 (Kinetics-400 的类别数) 改为 101。
```python
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`)指定。
## 修改数据集
MMAction2 支持 UCF101, Kinetics-400, Moments in Time, Multi-Moments in Time, THUMOS14,
Something-Something V1&V2, ActivityNet 等数据集。
用户可将自建数据集转换已有数据集格式。
对动作识别任务来讲,MMAction2 提供了 `RawframeDataset``VideoDataset` 等通用的数据集读取类,数据集格式相对简单。
`UCF101``RawframeDataset` 为例,
```python
# 数据集设置
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'
```
## 修改训练策略
通常情况下,设置较小的学习率,微调模型少量训练批次,即可取得较好效果。
```python
# 优化器
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)
```
## 使用预训练模型
若要将预训练模型用于整个网络(主干网络设置中的 `pretrained`,仅会在主干网络模型上加载预训练参数),可通过 `load_from` 指定模型文件路径或模型链接,实现预训练权重导入。
MMAction2 在 `configs/_base_/default_runtime.py` 文件中将 `load_from=None` 设为默认。由于配置文件的可继承性,用户可直接在下游配置文件中设置 `load_from` 的值来进行更改。
```python
# 将预训练模型用于整个 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 中找到
```
# 教程 3:如何增加新数据集
在本教程中,我们将介绍一些有关如何按已支持的数据格式进行数据组织,和组合已有数据集来自定义数据集的方法。
<!-- TOC -->
- [通过重组数据来自定义数据集](#%E9%80%9A%E8%BF%87%E9%87%8D%E7%BB%84%E6%95%B0%E6%8D%AE%E6%9D%A5%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E9%9B%86)
- [将数据集重新组织为现有格式](#%E5%B0%86%E6%95%B0%E6%8D%AE%E9%9B%86%E9%87%8D%E6%96%B0%E7%BB%84%E7%BB%87%E4%B8%BA%E7%8E%B0%E6%9C%89%E6%A0%BC%E5%BC%8F)
- [自定义数据集的示例](#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E9%9B%86%E7%9A%84%E7%A4%BA%E4%BE%8B)
- [通过组合已有数据集来自定义数据集](#%E9%80%9A%E8%BF%87%E7%BB%84%E5%90%88%E5%B7%B2%E6%9C%89%E6%95%B0%E6%8D%AE%E9%9B%86%E6%9D%A5%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E9%9B%86)
- [重复数据集](#%E9%87%8D%E5%A4%8D%E6%95%B0%E6%8D%AE%E9%9B%86)
<!-- TOC -->
## 通过重组数据来自定义数据集
### 将数据集重新组织为现有格式
最简单的方法是将数据集转换为现有的数据集格式(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](/mmaction/datasets/base.py) 基类编写一个新的数据集类,并重写三个抽象类方法:
`load_annotations(self)``evaluate(self, results, metrics, logger)``dump_results(self, results, out)`
[RawframeDataset](/mmaction/datasets/rawframe_dataset.py)[VideoDataset](/mmaction/datasets/video_dataset.py)[ActivityNetDataset](/mmaction/datasets/activitynet_dataset.py)
- 本地转换
用户可以转换标注文件格式为上述期望的格式,并将其存储为 pickle 或 json 文件,然后便可以应用于 `RawframeDataset``VideoDataset``ActivityNetDataset` 中。
数据预处理后,用户需要进一步修改配置文件以使用数据集。 这里展示了以帧形式使用自定义数据集的例子:
`configs/task/method/my_custom_config.py` 下:
```python
...
# 数据集设定
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,
...))
...
```
### 自定义数据集的示例
假设注释在文本文件中以新格式显示,并且图像文件名具有类似 “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` 中创建新数据集加载数据
```python
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`
```python
dataset_A_train = dict(
type='MyDataset',
ann_file=ann_file_train,
pipeline=train_pipeline
)
```
## 通过组合已有数据集来自定义数据集
MMAction2 还支持组合已有数据集以进行训练。 目前,它支持重复数据集(repeat dataset)。
### 重复数据集
MMAction2 使用 “RepeatDataset” 作为包装器来重复数据集。例如,假设原始数据集为 “Dataset_A”,
为了重复此数据集,可设置配置如下:
```python
dataset_A_train = dict(
type='RepeatDataset',
times=N,
dataset=dict( # 这是 Dataset_A 的原始配置
type='Dataset_A',
...
pipeline=train_pipeline
)
)
```
# 教程 4:如何设计数据处理流程
在本教程中,我们将介绍一些有关数据前处理流水线设计的方法,以及如何为项目自定义和扩展自己的数据流水线。
<!-- TOC -->
- [教程 4:如何设计数据处理流程](#%E6%95%99%E7%A8%8B-4%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B)
- [数据前处理流水线设计](#%E6%95%B0%E6%8D%AE%E5%89%8D%E5%A4%84%E7%90%86%E6%B5%81%E6%B0%B4%E7%BA%BF%E8%AE%BE%E8%AE%A1)
- [数据加载](#%E6%95%B0%E6%8D%AE%E5%8A%A0%E8%BD%BD)
- [数据预处理](#%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86)
- [数据格式化](#%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E5%8C%96)
- [扩展和使用自定义流水线](#%E6%89%A9%E5%B1%95%E5%92%8C%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B5%81%E6%B0%B4%E7%BA%BF)
<!-- TOC -->
## 数据前处理流水线设计
按照惯例,MMAction2 使用 `Dataset``DataLoader` 实现多进程数据加载。 `Dataset` 返回一个字典,作为模型的输入。
由于动作识别和时序动作检测的数据大小不一定相同(图片大小,边界框大小等),MMAction2 使用 MMCV 中的 `DataContainer` 收集和分配不同大小的数据,
详情可见 [这里](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py)
“数据前处理流水线” 和 “数据集构建” 是相互解耦的。通常,“数据集构建” 定义如何处理标注文件,“数据前处理流水线” 定义数据加载、预处理、格式化等功能(后文将详细介绍)。
数据前处理流水线由一系列相互解耦的操作组成。每个操作都输入一个字典(dict),新增/更新/删除相关字段,最终输出该字典,作为下一个操作的输入。
我们在下图中展示了一个典型的流水线。 蓝色块是流水线操作。
随着流水线的深入,每个操作都可以向结果字典添加新键(标记为绿色)或更新现有键(标记为橙色)。
![流水线](https://github.com/open-mmlab/mmaction2/raw/master/resources/data_pipeline.png)
这些操作分为数据加载,数据预处理和数据格式化。
这里以 TSN 的数据前处理流水线为例:
```python
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 运算符的数据前处理流水线的例子:
```python
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`。它以一个字典作为输入并返回一个字典
```python
from mmaction.datasets import PIPELINES
@PIPELINES.register_module()
class MyTransform:
def __call__(self, results):
results['key'] = value
return results
```
2. 导入新类
```python
from .my_pipeline import MyTransform
```
3. 在配置文件使用它
```python
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:如何添加新模块
在本教程中,我们将介绍一些有关如何为该项目定制优化器,开发新组件,以及添加新的学习率调整器(更新器)的方法。
<!-- TOC -->
- [自定义优化器](#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BC%98%E5%8C%96%E5%99%A8)
- [自定义优化器构造器](#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BC%98%E5%8C%96%E5%99%A8%E6%9E%84%E9%80%A0%E5%99%A8)
- [开发新组件](#%E5%BC%80%E5%8F%91%E6%96%B0%E7%BB%84%E4%BB%B6)
- [添加新的 backbones](#%E6%B7%BB%E5%8A%A0%E6%96%B0-backbones)
- [添加新的 heads](#%E6%B7%BB%E5%8A%A0%E6%96%B0-heads)
- [添加新的 loss function](#%E6%B7%BB%E5%8A%A0%E6%96%B0-loss-function)
- [添加新的学习率调节器(更新器)](#%E6%B7%BB%E5%8A%A0%E6%96%B0%E7%9A%84%E5%AD%A6%E4%B9%A0%E7%8E%87%E8%B0%83%E8%8A%82%E5%99%A8%EF%BC%88%E6%9B%B4%E6%96%B0%E5%99%A8%EF%BC%89)
<!-- TOC -->
## 自定义优化器
[CopyOfSGD](/mmaction/core/optimizer/copy_of_sgd.py) 是自定义优化器的一个例子,写在 `mmaction/core/optimizer/copy_of_sgd.py` 文件中。
更一般地,可以根据如下方法自定义优化器。
假设添加的优化器名为 `MyOptimizer`,它有 `a``b``c` 三个参数。
用户需要首先实现一个新的优化器文件,如 `mmaction/core/optimizer/my_optimizer.py`
```python
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` 中,从而让注册器可以找到这个新的模块并添加它:
```python
from .my_optimizer import MyOptimizer
```
之后,用户便可以在配置文件的 `optimizer` 字段中使用 `MyOptimizer`
在配置中,优化器由 `optimizer` 字段所定义,如下所示:
```python
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
```
用户可以直接根据 [PyTorch API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) 对参数进行直接设置。
## 自定义优化器构造器
某些模型可能对不同层的参数有特定的优化设置,例如 BatchNorm 层的梯度衰减。
用户可以通过自定义优化器构造函数来进行那些细粒度的参数调整。
用户可以编写一个基于 [DefaultOptimizerConstructor](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py) 的新的优化器构造器,
并且重写 `add_params(self, params, module)` 方法。
一个自定义优化器构造器的例子是 [TSMOptimizerConstructor](/mmaction/core/optimizer/tsm_optimizer_constructor.py)
更具体地,可以如下定义定制的优化器构造器。
`mmaction/core/optimizer/my_optimizer_constructor.py`
```python
from mmcv.runner import OPTIMIZER_BUILDERS, DefaultOptimizerConstructor
@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor(DefaultOptimizerConstructor):
```
`mmaction/core/optimizer/__init__.py`
```python
from .my_optimizer_constructor import MyOptimizerConstructor
```
之后便可在配置文件的 `optimizer` 域中使用 `MyOptimizerConstructor`
```python
# 优化器
optimizer = dict(
type='SGD',
constructor='MyOptimizerConstructor',
paramwise_cfg=dict(fc_lr5=True),
lr=0.02,
momentum=0.9,
weight_decay=0.0001)
```
## 开发新组件
MMAction2 将模型组件分为 4 种基础模型:
- 识别器(recognizer):整个识别器模型流水线,通常包含一个主干网络(backbone)和分类头(cls_head)。
- 主干网络(backbone):通常为一个用于提取特征的 FCN 网络,例如 ResNet,BNInception。
- 分类头(cls_head):用于分类任务的组件,通常包括一个带有池化层的 FC 层。
- 时序检测器(localizer):用于时序检测的模型,目前有的检测器包含 BSN,BMN,SSN。
### 添加新的 backbones
这里以 TSN 为例,说明如何开发新的组件。
1. 创建新文件 `mmaction/models/backbones/resnet.py`
```python
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` 中导入模型
```python
from .resnet import ResNet
```
3. 在配置文件中使用它
```python
model = dict(
...
backbone=dict(
type='ResNet',
arg1=xxx,
arg2=xxx),
)
```
### 添加新的 heads
这里以 TSNHead 为例,说明如何开发新的 head
1. 创建新文件 `mmaction/models/heads/tsn_head.py`
可以通过继承 [BaseHead](/mmaction/models/heads/base.py) 编写一个新的分类头,
并重写 `init_weights(self)``forward(self, x)` 方法
```python
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` 中导入模型
```python
from .tsn_head import TSNHead
```
3. 在配置文件中使用它
```python
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` 下进行实现。
```python
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`
```python
from .my_loss import MyLoss, my_loss
```
为了使用它,需要修改 `loss_xxx` 域。由于 MyLoss 用户识别任务,可以把它作为边界框损失 `loss_bbox`
```python
loss_bbox=dict(type='MyLoss'))
```
### 添加新的学习率调节器(更新器)
构造学习率更新器(即 PyTorch 中的 "scheduler")的默认方法是修改配置,例如:
```python
...
lr_config = dict(policy='step', step=[20, 40])
...
```
[`train.py`](/mmaction/apis/train.py) 的 api 中,它会在以下位置注册用于学习率更新的钩子:
```python
...
runner.register_training_hooks(
cfg.lr_config,
optimizer_config,
cfg.checkpoint_config,
cfg.log_config,
cfg.get('momentum_config', None))
...
```
到目前位置,所有支持的更新器可参考 [mmcv](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py)
但如果用户想自定义学习率更新器,则需要遵循以下步骤:
1. 首先,在 `$MMAction2/mmaction/core/scheduler` 编写自定义的学习率更新钩子(LrUpdaterHook)。以下片段是自定义学习率更新器的例子,它使用基于特定比率的学习率 `lrs`,并在每个 `steps` 处进行学习率衰减。以下代码段是自定义学习率更新器的例子:
```python
# 在此注册
@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` 变量
```python
lr_config = dict(policy='RelativeStep', steps=[20, 40, 60], lrs=[0.1, 0.01, 0.001])
```
更多例子可参考 [mmcv](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py)
# 教程 6:如何导出模型为 onnx 格式
开放式神经网络交换格式(Open Neural Network Exchange,即 [ONNX](https://onnx.ai/))是一个开放的生态系统,使 AI 开发人员能够随着项目的发展选择正确的工具。
<!-- TOC -->
- [支持的模型](#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A8%A1%E5%9E%8B)
- [如何使用](#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8)
- [准备工作](#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C)
- [行为识别器](#%E8%A1%8C%E4%B8%BA%E8%AF%86%E5%88%AB%E5%99%A8)
- [时序动作检测器](#%E6%97%B6%E5%BA%8F%E5%8A%A8%E4%BD%9C%E6%A3%80%E6%B5%8B%E5%99%A8)
<!-- TOC -->
## 支持的模型
到目前为止,MMAction2 支持将训练的 pytorch 模型中进行 onnx 导出。支持的模型有:
- I3D
- TSN
- TIN
- TSM
- R(2+1)D
- SLOWFAST
- SLOWONLY
- BMN
- BSN(tem, pem)
## 如何使用
对于简单的模型导出,用户可以使用这里的 [脚本](/tools/deployment/pytorch2onnx.py)
注意,需要安装 `onnx``onnxruntime` 包以进行导出后的验证。
### 准备工作
首先,安装 onnx
```shell
pip install onnx onnxruntime
```
MMAction2 提供了一个 python 脚本,用于将 MMAction2 训练的 pytorch 模型导出到 ONNX。
```shell
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`。目前仅支持行为识别器,不支持时序动作检测器。
### 行为识别器
对于行为识别器,可运行:
```shell
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --shape $SHAPE --verify
```
### 时序动作检测器
对于时序动作检测器,可运行:
```shell
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --is-localizer --shape $SHAPE --verify
```
如果发现提供的模型权重文件没有被成功导出,或者存在精度损失,可以在本 repo 下提出问题(issue)。
# 教程 7:如何自定义模型运行参数
在本教程中,我们将介绍如何在运行自定义模型时,进行自定义参数优化方法,学习率调整策略,工作流和钩子的方法。
<!-- TOC -->
- [定制优化方法](#%E5%AE%9A%E5%88%B6%E4%BC%98%E5%8C%96%E6%96%B9%E6%B3%95)
- [使用 PyTorch 内置的优化器](#%E4%BD%BF%E7%94%A8-PyTorch-%E5%86%85%E7%BD%AE%E7%9A%84%E4%BC%98%E5%8C%96%E5%99%A8)
- [定制用户自定义的优化器](#%E5%AE%9A%E5%88%B6%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E7%9A%84%E4%BC%98%E5%8C%96%E5%99%A8)
- [1. 定义一个新的优化器](#1-%E5%AE%9A%E4%B9%89%E4%B8%80%E4%B8%AA%E6%96%B0%E7%9A%84%E4%BC%98%E5%8C%96%E5%99%A8)
- [2. 注册优化器](#2-%E6%B3%A8%E5%86%8C%E4%BC%98%E5%8C%96%E5%99%A8)
- [3. 在配置文件中指定优化器](#3-%E5%9C%A8%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%AD%E6%8C%87%E5%AE%9A%E4%BC%98%E5%8C%96%E5%99%A8)
- [定制优化器构造器](#%E5%AE%9A%E5%88%B6%E4%BC%98%E5%8C%96%E5%99%A8%E6%9E%84%E9%80%A0%E5%99%A8)
- [额外设定](#%E9%A2%9D%E5%A4%96%E8%AE%BE%E5%AE%9A)
- [定制学习率调整策略](#%E5%AE%9A%E5%88%B6%E5%AD%A6%E4%B9%A0%E7%8E%87%E8%B0%83%E6%95%B4%E7%AD%96%E7%95%A5)
- [定制工作流](#%E5%AE%9A%E5%88%B6%E5%B7%A5%E4%BD%9C%E6%B5%81)
- [定制钩子](#%E5%AE%9A%E5%88%B6%E9%92%A9%E5%AD%90)
- [定制用户自定义钩子](#%E5%AE%9A%E5%88%B6%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89%E9%92%A9%E5%AD%90)
- [1. 创建一个新钩子](#1-%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E6%96%B0%E9%92%A9%E5%AD%90)
- [2. 注册新钩子](#2-%E6%B3%A8%E5%86%8C%E6%96%B0%E9%92%A9%E5%AD%90)
- [3. 修改配置](#3-%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE)
- [使用 MMCV 内置钩子](#%E4%BD%BF%E7%94%A8-MMCV-%E5%86%85%E7%BD%AE%E9%92%A9%E5%AD%90)
- [修改默认运行的钩子](#%E4%BF%AE%E6%94%B9%E9%BB%98%E8%AE%A4%E8%BF%90%E8%A1%8C%E7%9A%84%E9%92%A9%E5%AD%90)
- [模型权重文件配置](#%E6%A8%A1%E5%9E%8B%E6%9D%83%E9%87%8D%E6%96%87%E4%BB%B6%E9%85%8D%E7%BD%AE)
- [日志配置](#%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AE)
- [验证配置](#%E9%AA%8C%E8%AF%81%E9%85%8D%E7%BD%AE)
<!-- TOC -->
## 定制优化方法
### 使用 PyTorch 内置的优化器
MMAction2 支持 PyTorch 实现的所有优化器,仅需在配置文件中,指定 “optimizer” 字段
例如,如果要使用 “Adam”,则修改如下。
```python
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
```
要修改模型的学习率,用户只需要在优化程序的配置中修改 “lr” 即可。
用户可根据 [PyTorch API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) 进行参数设置
例如,如果想使用 `Adam` 并设置参数为 `torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)`
则需要进行如下修改
```python
optimizer = dict(type='Adam', lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
```
### 定制用户自定义的优化器
#### 1. 定义一个新的优化器
一个自定义的优化器可根据如下规则进行定制
假设用户想添加一个名为 `MyOptimzer` 的优化器,其拥有参数 `a`, `b``c`
可以创建一个名为 `mmaction/core/optimizer` 的文件夹,并在目录下的文件进行构建,如 `mmaction/core/optimizer/my_optimizer.py`
```python
from mmcv.runner import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c):
```
#### 2. 注册优化器
要找到上面定义的上述模块,首先应将此模块导入到主命名空间中。有两种方法可以实现它。
- 修改 `mmaction/core/optimizer/__init__.py` 来进行调用
新定义的模块应导入到 `mmaction/core/optimizer/__init__.py` 中,以便注册器能找到新模块并将其添加:
```python
from .my_optimizer import MyOptimizer
```
- 在配置中使用 `custom_imports` 手动导入
```python
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` **不会** 被直接导入。
#### 3. 在配置文件中指定优化器
之后,用户便可在配置文件的 `optimizer` 域中使用 `MyOptimizer`
在配置中,优化器由 “optimizer” 字段定义,如下所示:
```python
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
```
要使用自定义的优化器,可以将该字段更改为
```python
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
```
### 定制优化器构造器
某些模型可能具有一些特定于参数的设置以进行优化,例如 BatchNorm 层的权重衰减。
用户可以通过自定义优化器构造函数来进行那些细粒度的参数调整。
```python
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
```
默认的优化器构造器被创建于[](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/optimizer/default_constructor.py#L11)
可被视为新优化器构造器的模板。
### 额外设定
优化器没有实现的优化技巧(trick)可通过优化器构造函数(例如,设置按参数的学习率)或钩子来实现。
下面列出了一些可以稳定训练或加快训练速度的常用设置。用户亦可通过为 MMAction2 创建 PR,发布更多设置。
- __使用梯度裁剪来稳定训练__
一些模型需要使用梯度裁剪来剪辑渐变以稳定训练过程。 一个例子如下:
```python
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
```
- __使用动量调整来加速模型收敛__
MMAction2 支持动量调整器根据学习率修改模型的动量,从而使模型收敛更快。
动量调整程序通常与学习率调整器一起使用,例如,以下配置用于3D检测以加速收敛。
更多细节可参考 [CyclicLrUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L327)
[CyclicMomentumUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/momentum_updater.py#L130)
```python
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,
)
```
## 定制学习率调整策略
在配置文件中使用默认值的逐步学习率调整,它调用 MMCV 中的 [`StepLRHook`](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L153)
此外,也支持其他学习率调整方法,如 `CosineAnnealing``Poly`。 详情可见 [这里](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py)
- Poly:
```python
lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
```
- ConsineAnnealing:
```python
lr_config = dict(
policy='CosineAnnealing',
warmup='linear',
warmup_iters=1000,
warmup_ratio=1.0 / 10,
min_lr_ratio=1e-5)
```
## 定制工作流
默认情况下,MMAction2 推荐用户在训练周期中使用 “EvalHook” 进行模型验证,也可以选择 “val” 工作流模型进行模型验证。
工作流是一个形如 (工作流名, 周期数) 的列表,用于指定运行顺序和周期。其默认设置为:
```python
workflow = [('train', 1)]
```
其代表要进行一轮周期的训练。
有时,用户可能希望检查有关验证集中模型的某些指标(例如,损失,准确性)。
在这种情况下,可以将工作流程设置为
```python
[('train', 1), ('val', 1)]
```
从而将迭代运行1个训练时间和1个验证时间。
**值得注意的是**
1. 在验证周期时不会更新模型参数。
2. 配置文件内的关键词 `total_epochs` 控制训练时期数,并且不会影响验证工作流程。
3. 工作流 `[('train', 1), ('val', 1)]``[('train', 1)]` 不会改变 `EvalHook` 的行为。
因为 `EvalHook``after_train_epoch` 调用,而验证工作流只会影响 `after_val_epoch` 调用的钩子。
因此,`[('train', 1), ('val', 1)]``[('train', 1)]` 的区别在于,runner 在完成每一轮训练后,会计算验证集上的损失。
## 定制钩子
### 定制用户自定义钩子
#### 1. 创建一个新钩子
这里举一个在 MMAction2 中创建一个新钩子,并在训练中使用它的示例:
```python
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_run``after_run``before_epoch``after_epoch``before_iter``after_iter`
#### 2. 注册新钩子
之后,需要导入 `MyHook`。假设该文件在 `mmaction/core/utils/my_hook.py`,有两种办法导入它:
- 修改 `mmaction/core/utils/__init__.py` 进行导入
新定义的模块应导入到 `mmaction/core/utils/__init__py` 中,以便注册表能找到并添加新模块:
```python
from .my_hook import MyHook
```
- 使用配置文件中的 `custom_imports` 变量手动导入
```python
custom_imports = dict(imports=['mmaction.core.utils.my_hook'], allow_failed_imports=False)
```
#### 3. 修改配置
```python
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value)
]
```
还可通过 `priority` 参数(可选参数值包括 `'NORMAL'``'HIGHEST'`)设置钩子优先级,如下所示:
```python
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]
```
默认情况下,在注册过程中,钩子的优先级设置为 “NORMAL”。
### 使用 MMCV 内置钩子
如果该钩子已在 MMCV 中实现,则可以直接修改配置以使用该钩子,如下所示
```python
mmcv_hooks = [
dict(type='MMCVHook', a=a_value, b=b_value, priority='NORMAL')
]
```
### 修改默认运行的钩子
有一些常见的钩子未通过 `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`](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/hooks/checkpoint.py#L9)
```python
checkpoint_config = dict(interval=1)
```
用户可以设置 “max_keep_ckpts” 来仅保存少量模型权重文件,或者通过 “save_optimizer” 决定是否存储优化器的状态字典。
更多细节可参考 [这里](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.CheckpointHook)
#### 日志配置
`log_config` 包装了多个记录器钩子,并可以设置间隔。
目前,MMCV 支持 `WandbLoggerHook``MlflowLoggerHook``TensorboardLoggerHook`
更多细节可参考[这里](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.LoggerHook)
```python
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
dict(type='TensorboardLoggerHook')
])
```
#### 验证配置
评估的配置将用于初始化 [`EvalHook`](https://github.com/open-mmlab/mmaction2/blob/master/mmaction/core/evaluation/eval_hooks.py#L12)
除了键 `interval` 外,其他参数,如 “metrics” 也将传递给 `dataset.evaluate()`
```python
evaluation = dict(interval=1, metrics='bbox')
```
除了训练/测试脚本外,MMAction2 还在 `tools/` 目录下提供了许多有用的工具。
## 目录
<!-- TOC -->
- [日志分析](#%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90)
- [模型复杂度分析](#%E6%A8%A1%E5%9E%8B%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90)
- [模型转换](#%E6%A8%A1%E5%9E%8B%E8%BD%AC%E6%8D%A2)
- [导出 MMAction2 模型为 ONNX 格式(实验特性)](#%E5%AF%BC%E5%87%BA-MMAction2-%E6%A8%A1%E5%9E%8B%E4%B8%BA-ONNX-%E6%A0%BC%E5%BC%8F%EF%BC%88%E5%AE%9E%E9%AA%8C%E7%89%B9%E6%80%A7%EF%BC%89)
- [发布模型](#%E5%8F%91%E5%B8%83%E6%A8%A1%E5%9E%8B)
- [其他脚本](#%E5%85%B6%E4%BB%96%E8%84%9A%E6%9C%AC)
- [指标评价](#%E6%8C%87%E6%A0%87%E8%AF%84%E4%BB%B7)
- [打印完整配置](#%E6%89%93%E5%8D%B0%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE)
<!-- TOC -->
## 日志分析
输入变量指定一个训练日志文件,可通过 `tools/analysis/analyze_logs.py` 脚本绘制 loss/top-k 曲线。本功能依赖于 `seaborn`,使用前请先通过 `pip install seaborn` 安装依赖包。
![准确度曲线图](https://github.com/open-mmlab/mmaction2/raw/master/resources/acc_curve.png)
```shell
python tools/analysis/analyze_logs.py plot_curve ${JSON_LOGS} [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]
```
例如:
- 绘制某日志文件对应的分类损失曲线图。
```shell
python tools/analysis/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls
```
- 绘制某日志文件对应的 top-1 和 top-5 准确率曲线图,并将曲线图导出为 PDF 文件。
```shell
python tools/analysis/analyze_logs.py plot_curve log.json --keys top1_acc top5_acc --out results.pdf
```
- 在同一图像内绘制两份日志文件对应的 top-1 准确率曲线图。
```shell
python tools/analysis/analyze_logs.py plot_curve log1.json log2.json --keys top1_acc --legend run1 run2
```
用户还可以通过本工具计算平均训练速度。
```shell
python tools/analysis/analyze_logs.py cal_train_time ${JSON_LOGS} [--include-outliers]
```
- 计算某日志文件对应的平均训练速度。
```shell
python tools/analysis/analyze_logs.py cal_train_time work_dirs/some_exp/20200422_153324.log.json
```
预计输出结果如下所示:
```text
-----Analyze train time of work_dirs/some_exp/20200422_153324.log.json-----
slowest epoch 60, average time is 0.9736
fastest epoch 18, average time is 0.9001
time std over epochs is 0.0177
average iter time: 0.9330 s/iter
```
## 模型复杂度分析
`/tools/analysis/get_flops.py` 是根据 [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) 库改编的脚本,用于计算输入变量指定模型的 FLOPs 和参数量。
```shell
python tools/analysis/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
```
预计输出结果如下所示:
```text
==============================
Input shape: (1, 3, 32, 340, 256)
Flops: 37.1 GMac
Params: 28.04 M
==============================
```
**注意**:该工具仍处于试验阶段,不保证该数字绝对正确。
用户可以将结果用于简单比较,但若要在技术报告或论文中采用该结果,请仔细检查。
(1) FLOPs 与输入变量形状有关,但是模型的参数量与输入变量形状无关。2D 行为识别器的默认形状为 (1, 3, 340, 256),3D 行为识别器的默认形状为 (1, 3, 32, 340, 256)。
(2) 部分算子不参与 FLOPs 以及参数量的计算,如 GN 和一些自定义算子。更多详细信息请参考 [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py)
## 模型转换
### 导出 MMAction2 模型为 ONNX 格式(实验特性)
`/tools/deployment/pytorch2onnx.py` 脚本用于将模型转换为 [ONNX](https://github.com/onnx/onnx) 格式。
同时,该脚本支持比较 PyTorch 模型和 ONNX 模型的输出结果,验证输出结果是否相同。
本功能依赖于 `onnx` 以及 `onnxruntime`,使用前请先通过 `pip install onnx onnxruntime` 安装依赖包。
请注意,可通过 `--softmax` 选项在行为识别器末尾添加 Softmax 层,从而获取 `[0, 1]` 范围内的预测结果。
- 对于行为识别模型,请运行:
```shell
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --shape $SHAPE --verify
```
- 对于时序动作检测模型,请运行:
```shell
python tools/deployment/pytorch2onnx.py $CONFIG_PATH $CHECKPOINT_PATH --is-localizer --shape $SHAPE --verify
```
### 发布模型
`tools/deployment/publish_model.py` 脚本用于进行模型发布前的准备工作,主要包括:
(1) 将模型的权重张量转化为 CPU 张量。
(2) 删除优化器状态信息。
(3) 计算模型权重文件的哈希值,并将哈希值添加到文件名后。
```shell
python tools/deployment/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
```
例如,
```shell
python tools/deployment/publish_model.py work_dirs/tsn_r50_1x1x3_100e_kinetics400_rgb/latest.pth tsn_r50_1x1x3_100e_kinetics400_rgb.pth
```
最终,输出文件名为 `tsn_r50_1x1x3_100e_kinetics400_rgb-{hash id}.pth`。
## 其他脚本
### 指标评价
`tools/analysis/eval_metric.py` 脚本通过输入变量指定配置文件,以及对应的结果存储文件,计算某一评价指标。
结果存储文件通过 `tools/test.py` 脚本(通过参数 `--out ${RESULT_FILE}` 指定)生成,保存了指定模型在指定数据集中的预测结果。
```shell
python tools/analysis/eval_metric.py ${CONFIG_FILE} ${RESULT_FILE} [--eval ${EVAL_METRICS}] [--cfg-options ${CFG_OPTIONS}] [--eval-options ${EVAL_OPTIONS}]
```
### 打印完整配置
`tools/analysis/print_config.py` 脚本会解析所有输入变量,并打印完整配置信息。
```shell
python tools/print_config.py ${CONFIG} [-h] [--options ${OPTIONS [OPTIONS...]}]
```
### 检查视频
`tools/analysis/check_videos.py` 脚本利用指定视频编码器,遍历指定配置文件视频数据集中所有样本,寻找无效视频文件(文件破损或者文件不存在),并将无效文件路径保存到输出文件中。请注意,删除无效视频文件后,需要重新生成视频文件列表。
```shell
python tools/analysis/check_videos.py ${CONFIG} [-h] [--options OPTIONS [OPTIONS ...]] [--cfg-options CFG_OPTIONS [CFG_OPTIONS ...]] [--output-file OUTPUT_FILE] [--split SPLIT] [--decoder DECODER] [--num-processes NUM_PROCESSES] [--remove-corrupted-videos]
```
# Copyright (c) OpenMMLab. All rights reserved.
import mmcv
from mmcv import digit_version
from .version import __version__
mmcv_minimum_version = '1.3.6'
mmcv_maximum_version = '1.7.0'
mmcv_version = digit_version(mmcv.__version__)
assert (digit_version(mmcv_minimum_version) <= mmcv_version
<= digit_version(mmcv_maximum_version)), \
f'MMCV=={mmcv.__version__} is used but incompatible. ' \
f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.'
__all__ = ['__version__']
# Copyright (c) OpenMMLab. All rights reserved.
from .inference import inference_recognizer, init_recognizer
from .test import multi_gpu_test, single_gpu_test
from .train import init_random_seed, train_model
__all__ = [
'train_model', 'init_recognizer', 'inference_recognizer', 'multi_gpu_test',
'single_gpu_test', 'init_random_seed'
]
# Copyright (c) OpenMMLab. All rights reserved.
import os
import os.path as osp
import re
import warnings
from operator import itemgetter
import mmcv
import numpy as np
import torch
from mmcv.parallel import collate, scatter
from mmcv.runner import load_checkpoint
from mmaction.core import OutputHook
from mmaction.datasets.pipelines import Compose
from mmaction.models import build_recognizer
def init_recognizer(config, checkpoint=None, device='cuda:0', **kwargs):
"""Initialize a recognizer from config file.
Args:
config (str | :obj:`mmcv.Config`): Config file path or the config
object.
checkpoint (str | None, optional): Checkpoint path/url. If set to None,
the model will not load any weights. Default: None.
device (str | :obj:`torch.device`): The desired device of returned
tensor. Default: 'cuda:0'.
Returns:
nn.Module: The constructed recognizer.
"""
if 'use_frames' in kwargs:
warnings.warn('The argument `use_frames` is deprecated PR #1191. '
'Now you can use models trained with frames or videos '
'arbitrarily. ')
if isinstance(config, str):
config = mmcv.Config.fromfile(config)
elif not isinstance(config, mmcv.Config):
raise TypeError('config must be a filename or Config object, '
f'but got {type(config)}')
# pretrained model is unnecessary since we directly load checkpoint later
config.model.backbone.pretrained = None
model = build_recognizer(config.model, test_cfg=config.get('test_cfg'))
if checkpoint is not None:
load_checkpoint(model, checkpoint, map_location='cpu')
model.cfg = config
model.to(device)
model.eval()
return model
def inference_recognizer(model, video, outputs=None, as_tensor=True, **kwargs):
"""Inference a video with the recognizer.
Args:
model (nn.Module): The loaded recognizer.
video (str | dict | ndarray): The video file path / url or the
rawframes directory path / results dictionary (the input of
pipeline) / a 4D array T x H x W x 3 (The input video).
outputs (list(str) | tuple(str) | str | None) : Names of layers whose
outputs need to be returned, default: None.
as_tensor (bool): Same as that in ``OutputHook``. Default: True.
Returns:
dict[tuple(str, float)]: Top-5 recognition result dict.
dict[torch.tensor | np.ndarray]:
Output feature maps from layers specified in `outputs`.
"""
if 'use_frames' in kwargs:
warnings.warn('The argument `use_frames` is deprecated PR #1191. '
'Now you can use models trained with frames or videos '
'arbitrarily. ')
if 'label_path' in kwargs:
warnings.warn('The argument `use_frames` is deprecated PR #1191. '
'Now the label file is not needed in '
'inference_recognizer. ')
input_flag = None
if isinstance(video, dict):
input_flag = 'dict'
elif isinstance(video, np.ndarray):
assert len(video.shape) == 4, 'The shape should be T x H x W x C'
input_flag = 'array'
elif isinstance(video, str) and video.startswith('http'):
input_flag = 'video'
elif isinstance(video, str) and osp.exists(video):
if osp.isfile(video):
if video.endswith('.npy'):
input_flag = 'audio'
else:
input_flag = 'video'
if osp.isdir(video):
input_flag = 'rawframes'
else:
raise RuntimeError('The type of argument video is not supported: '
f'{type(video)}')
if isinstance(outputs, str):
outputs = (outputs, )
assert outputs is None or isinstance(outputs, (tuple, list))
cfg = model.cfg
device = next(model.parameters()).device # model device
# build the data pipeline
test_pipeline = cfg.data.test.pipeline
# Alter data pipelines & prepare inputs
if input_flag == 'dict':
data = video
if input_flag == 'array':
modality_map = {2: 'Flow', 3: 'RGB'}
modality = modality_map.get(video.shape[-1])
data = dict(
total_frames=video.shape[0],
label=-1,
start_index=0,
array=video,
modality=modality)
for i in range(len(test_pipeline)):
if 'Decode' in test_pipeline[i]['type']:
test_pipeline[i] = dict(type='ArrayDecode')
test_pipeline = [x for x in test_pipeline if 'Init' not in x['type']]
if input_flag == 'video':
data = dict(filename=video, label=-1, start_index=0, modality='RGB')
if 'Init' not in test_pipeline[0]['type']:
test_pipeline = [dict(type='OpenCVInit')] + test_pipeline
else:
test_pipeline[0] = dict(type='OpenCVInit')
for i in range(len(test_pipeline)):
if 'Decode' in test_pipeline[i]['type']:
test_pipeline[i] = dict(type='OpenCVDecode')
if input_flag == 'rawframes':
filename_tmpl = cfg.data.test.get('filename_tmpl', 'img_{:05}.jpg')
modality = cfg.data.test.get('modality', 'RGB')
start_index = cfg.data.test.get('start_index', 1)
# count the number of frames that match the format of `filename_tmpl`
# RGB pattern example: img_{:05}.jpg -> ^img_\d+.jpg$
# Flow patteren example: {}_{:05d}.jpg -> ^x_\d+.jpg$
pattern = f'^{filename_tmpl}$'
if modality == 'Flow':
pattern = pattern.replace('{}', 'x')
pattern = pattern.replace(
pattern[pattern.find('{'):pattern.find('}') + 1], '\\d+')
total_frames = len(
list(
filter(lambda x: re.match(pattern, x) is not None,
os.listdir(video))))
data = dict(
frame_dir=video,
total_frames=total_frames,
label=-1,
start_index=start_index,
filename_tmpl=filename_tmpl,
modality=modality)
if 'Init' in test_pipeline[0]['type']:
test_pipeline = test_pipeline[1:]
for i in range(len(test_pipeline)):
if 'Decode' in test_pipeline[i]['type']:
test_pipeline[i] = dict(type='RawFrameDecode')
if input_flag == 'audio':
data = dict(
audio_path=video,
total_frames=len(np.load(video)),
start_index=cfg.data.test.get('start_index', 1),
label=-1)
test_pipeline = Compose(test_pipeline)
data = test_pipeline(data)
data = collate([data], samples_per_gpu=1)
if next(model.parameters()).is_cuda:
# scatter to specified GPU
data = scatter(data, [device])[0]
# forward the model
with OutputHook(model, outputs=outputs, as_tensor=as_tensor) as h:
with torch.no_grad():
scores = model(return_loss=False, **data)[0]
returned_features = h.layer_outputs if outputs else None
num_classes = scores.shape[-1]
score_tuples = tuple(zip(range(num_classes), scores))
score_sorted = sorted(score_tuples, key=itemgetter(1), reverse=True)
top5_label = score_sorted[:5]
if outputs:
return top5_label, returned_features
return top5_label
# Copyright (c) OpenMMLab. All rights reserved.
import os.path as osp
import pickle
import shutil
import tempfile
# TODO import test functions from mmcv and delete them from mmaction2
import warnings
import mmcv
import torch
import torch.distributed as dist
from mmcv.runner import get_dist_info
try:
from mmcv.engine import (collect_results_cpu, collect_results_gpu,
multi_gpu_test, single_gpu_test)
from_mmcv = True
except (ImportError, ModuleNotFoundError):
warnings.warn(
'DeprecationWarning: single_gpu_test, multi_gpu_test, '
'collect_results_cpu, collect_results_gpu from mmaction2 will be '
'deprecated. Please install mmcv through master branch.')
from_mmcv = False
if not from_mmcv:
def single_gpu_test(model, data_loader): # noqa: F811
"""Test model with a single gpu.
This method tests model with a single gpu and
displays test progress bar.
Args:
model (nn.Module): Model to be tested.
data_loader (nn.Dataloader): Pytorch data loader.
Returns:
list: The prediction results.
"""
model.eval()
results = []
dataset = data_loader.dataset
prog_bar = mmcv.ProgressBar(len(dataset))
for data in data_loader:
with torch.no_grad():
result = model(return_loss=False, **data)
results.extend(result)
# use the first key as main key to calculate the batch size
batch_size = len(next(iter(data.values())))
for _ in range(batch_size):
prog_bar.update()
return results
def multi_gpu_test( # noqa: F811
model, data_loader, tmpdir=None, gpu_collect=True):
"""Test model with multiple gpus.
This method tests model with multiple gpus and collects the results
under two different modes: gpu and cpu modes. By setting
'gpu_collect=True' it encodes results to gpu tensors and use gpu
communication for results collection. On cpu mode it saves the results
on different gpus to 'tmpdir' and collects them by the rank 0 worker.
Args:
model (nn.Module): Model to be tested.
data_loader (nn.Dataloader): Pytorch data loader.
tmpdir (str): Path of directory to save the temporary results from
different gpus under cpu mode. Default: None
gpu_collect (bool): Option to use either gpu or cpu to collect
results. Default: True
Returns:
list: The prediction results.
"""
model.eval()
results = []
dataset = data_loader.dataset
rank, world_size = get_dist_info()
if rank == 0:
prog_bar = mmcv.ProgressBar(len(dataset))
for data in data_loader:
with torch.no_grad():
result = model(return_loss=False, **data)
results.extend(result)
if rank == 0:
# use the first key as main key to calculate the batch size
batch_size = len(next(iter(data.values())))
for _ in range(batch_size * world_size):
prog_bar.update()
# collect results from all ranks
if gpu_collect:
results = collect_results_gpu(results, len(dataset))
else:
results = collect_results_cpu(results, len(dataset), tmpdir)
return results
def collect_results_cpu(result_part, size, tmpdir=None): # noqa: F811
"""Collect results in cpu mode.
It saves the results on different gpus to 'tmpdir' and collects
them by the rank 0 worker.
Args:
result_part (list): Results to be collected
size (int): Result size.
tmpdir (str): Path of directory to save the temporary results from
different gpus under cpu mode. Default: None
Returns:
list: Ordered results.
"""
rank, world_size = get_dist_info()
# create a tmp dir if it is not specified
if tmpdir is None:
MAX_LEN = 512
# 32 is whitespace
dir_tensor = torch.full((MAX_LEN, ),
32,
dtype=torch.uint8,
device='cuda')
if rank == 0:
mmcv.mkdir_or_exist('.dist_test')
tmpdir = tempfile.mkdtemp(dir='.dist_test')
tmpdir = torch.tensor(
bytearray(tmpdir.encode()),
dtype=torch.uint8,
device='cuda')
dir_tensor[:len(tmpdir)] = tmpdir
dist.broadcast(dir_tensor, 0)
tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
else:
tmpdir = osp.join(tmpdir, '.dist_test')
mmcv.mkdir_or_exist(tmpdir)
# synchronizes all processes to make sure tmpdir exist
dist.barrier()
# dump the part result to the dir
mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
# synchronizes all processes for loading pickle file
dist.barrier()
# collect all parts
if rank != 0:
return None
# load results of all parts from tmp dir
part_list = []
for i in range(world_size):
part_file = osp.join(tmpdir, f'part_{i}.pkl')
part_list.append(mmcv.load(part_file))
# sort the results
ordered_results = []
for res in zip(*part_list):
ordered_results.extend(list(res))
# the dataloader may pad some samples
ordered_results = ordered_results[:size]
# remove tmp dir
shutil.rmtree(tmpdir)
return ordered_results
def collect_results_gpu(result_part, size): # noqa: F811
"""Collect results in gpu mode.
It encodes results to gpu tensors and use gpu communication for results
collection.
Args:
result_part (list): Results to be collected
size (int): Result size.
Returns:
list: Ordered results.
"""
rank, world_size = get_dist_info()
# dump result part to tensor with pickle
part_tensor = torch.tensor(
bytearray(pickle.dumps(result_part)),
dtype=torch.uint8,
device='cuda')
# gather all result part tensor shape
shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
shape_list = [shape_tensor.clone() for _ in range(world_size)]
dist.all_gather(shape_list, shape_tensor)
# padding result part tensor to max length
shape_max = torch.tensor(shape_list).max()
part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
part_send[:shape_tensor[0]] = part_tensor
part_recv_list = [
part_tensor.new_zeros(shape_max) for _ in range(world_size)
]
# gather all result part
dist.all_gather(part_recv_list, part_send)
if rank == 0:
part_list = []
for recv, shape in zip(part_recv_list, shape_list):
part_list.append(
pickle.loads(recv[:shape[0]].cpu().numpy().tobytes()))
# sort the results
ordered_results = []
for res in zip(*part_list):
ordered_results.extend(list(res))
# the dataloader may pad some samples
ordered_results = ordered_results[:size]
return ordered_results
return None
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment