MMAction2-1.2.0文档

新手入门

安装

python torch torchaudio torchvision cuda mmcv
3.8.10 2.0.0 2.0.1 0.15.1 12.1.1 2.1.0
  • mmaction2.1.2要求mmcv<2.2.0
bash 复制代码
# conda create -n openmmlab python=3.8.10
# python -m pip install --upgrade pip
# pip install torch==2.0.0 torchaudio==2.0.1 torchvision==0.15.1
pip install -U openmim
mim install mmengine
# linux版本
pip install mmcv==2.1.0 -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html
# win版本
pip install mmcv==2.1.0 -f https://download.openmmlab.com/mmcv/dist/cpu/torch2.0/index.html
git clone https://github.com/open-mmlab/mmaction2.git
cd mmaction2
pip install -v -e .
# "-v" 表示输出更多安装相关的信息
# "-e" 表示以可编辑形式安装,这样可以在不重新安装的情况下,让本地修改直接生效。

点击跳转MMCV官方安装教程

快速运行

推理

bash 复制代码
python demo/demo_inferencer.py  demo/demo.mp4 \
    --rec tsn --print-result \
    --label-file tools/data/kinetics/label_map_k400.txt
# 推理结果
# {'predictions': [{'rec_labels': [[6]], 'rec_scores': [[...]]}]}

准备数据集

bash 复制代码
# 下载官方预先准备好的 kinetics400_tiny.zip ,并将其解压到 MMAction2 根目录下的 data/ 目录
wget https://download.openmmlab.com/mmaction/kinetics400_tiny.zip
mkdir -p data/
unzip kinetics400_tiny.zip -d data/

修改配置

使用 resnet50 作为主干网络来训练 TSN。由于 MMAction2 已经有了完整的 Kinetics400 数据集的配置文件 (configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py),只需要在其基础上进行一些修改。

python 复制代码
# 修改数据集
# 打开 configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py ,按如下替换关键字:
data_root = 'data/kinetics400_tiny/train'
data_root_val = 'data/kinetics400_tiny/val'
ann_file_train = 'data/kinetics400_tiny/kinetics_tiny_train_video.txt'
ann_file_val = 'data/kinetics400_tiny/kinetics_tiny_val_video.txt'

# 修改运行配置
# 由于数据集的大小减少,我们建议将训练批大小减少到4个,训练epoch的数量相应减少到10个。此外,我们建议将验证和权值存储间隔缩短为1轮,并修改学习率衰减策略。修改 configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py 中对应的关键字,如下所示生效。
# 设置训练批大小为 4
train_dataloader['batch_size'] = 4

# 每轮都保存权重,并且只保留最新的权重
default_hooks = dict(
    checkpoint=dict(type='CheckpointHook', interval=1, max_keep_ckpts=1))
# 将最大 epoch 数设置为 10,并每 1 个 epoch验证模型
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=10, val_interval=1)
#根据 10 个 epoch调整学习率调度
param_scheduler = [
    dict(
        type='MultiStepLR',
        begin=0,
        end=10,
        by_epoch=True,
        milestones=[4, 8],
        gamma=0.1)
# 修改模型配置
# 由于精简版 Kinetics 数据集规模较小,建议加载原始 Kinetics 数据集上的预训练模型。此外,模型需要根据实际类别数进行修改。请直接将以下代码添加到 configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py 中。
model = dict(
    cls_head=dict(num_classes=2))
load_from = 'https://download.openmmlab.com/mmaction/v1.0/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb_20220906-cd10898e.pth'
# 在这里,我们直接通过继承 ({external+mmengine:doc} MMEngine: Config <advanced_tutorials/ Config>) 机制重写了基本配置中的相应参数。原始字段分布在 configs/_base_/models/tsn_r50.py、configs/_base_/schedules/sgd_100e.py 和 configs/_base_/default_runtime.py中。

训练

bash 复制代码
python tools/train.py configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py
# 在没有额外配置的情况下,模型权重将被保存到 work_dirs/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb/,而日志将被存储到 work_dirs/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb/。

测试

20分钟了解 MMAction2 框架设计

步骤3:构建一个识别器

python 复制代码
# 修改此处 predictions[0].pred_score -> predictions[0].pred_scores.item
print('Scores of Sample[0]', predictions[0].pred_scores.item)

步骤4:构建一个评估指标

python 复制代码
# 修改此处 data_sample['pred_score'].cpu().numpy() -> data_sample['pred_scores']['item']
scores = data_sample['pred_scores']['item']
# 并在下方额外添加 scores = np.array(scores)
scores = np.array(scores)

步骤5:使用本地 PyTorch 训练和测试

python 复制代码
'''
修改此处 for data_batch in track_iter_progress(val_data_loader): ->
task_num = len(val_data_loader)
for data_batch in track_iter_progress((val_data_loader,task_num)):
'''
task_num = len(val_data_loader)
for data_batch in track_iter_progress((val_data_loader,task_num)):

修改完的完整代码

python 复制代码
from mmaction.utils import register_all_modules

register_all_modules(init_default_scope=True)
print('**************************步骤0:准备数据*****************************')
print('**************************步骤1:构建一个数据流水线*****************************')
import mmcv
import decord
import numpy as np
from mmcv.transforms import TRANSFORMS, BaseTransform, to_tensor
from mmaction.structures import ActionDataSample


@TRANSFORMS.register_module()
class VideoInit(BaseTransform):
    def transform(self, results):
        container = decord.VideoReader(results['filename'])
        results['total_frames'] = len(container)
        results['video_reader'] = container
        return results


@TRANSFORMS.register_module()
class VideoSample(BaseTransform):
    def __init__(self, clip_len, num_clips, test_mode=False):
        self.clip_len = clip_len
        self.num_clips = num_clips
        self.test_mode = test_mode

    def transform(self, results):
        total_frames = results['total_frames']
        interval = total_frames // self.clip_len

        if self.test_mode:
            # 使测试期间的采样具有确定性
            np.random.seed(42)

        inds_of_all_clips = []
        for i in range(self.num_clips):
            bids = np.arange(self.clip_len) * interval
            offset = np.random.randint(interval, size=bids.shape)
            inds = bids + offset
            inds_of_all_clips.append(inds)

        results['frame_inds'] = np.concatenate(inds_of_all_clips)
        results['clip_len'] = self.clip_len
        results['num_clips'] = self.num_clips
        return results


@TRANSFORMS.register_module()
class VideoDecode(BaseTransform):
    def transform(self, results):
        frame_inds = results['frame_inds']
        container = results['video_reader']

        imgs = container.get_batch(frame_inds).asnumpy()
        imgs = list(imgs)

        results['video_reader'] = None
        del container

        results['imgs'] = imgs
        results['img_shape'] = imgs[0].shape[:2]
        return results


@TRANSFORMS.register_module()
class VideoResize(BaseTransform):
    def __init__(self, r_size):
        self.r_size = (np.inf, r_size)

    def transform(self, results):
        img_h, img_w = results['img_shape']
        new_w, new_h = mmcv.rescale_size((img_w, img_h), self.r_size)

        imgs = [mmcv.imresize(img, (new_w, new_h))
                for img in results['imgs']]
        results['imgs'] = imgs
        results['img_shape'] = imgs[0].shape[:2]
        return results


@TRANSFORMS.register_module()
class VideoCrop(BaseTransform):
    def __init__(self, c_size):
        self.c_size = c_size

    def transform(self, results):
        img_h, img_w = results['img_shape']
        center_x, center_y = img_w // 2, img_h // 2
        x1, x2 = center_x - self.c_size // 2, center_x + self.c_size // 2
        y1, y2 = center_y - self.c_size // 2, center_y + self.c_size // 2
        imgs = [img[y1:y2, x1:x2] for img in results['imgs']]
        results['imgs'] = imgs
        results['img_shape'] = imgs[0].shape[:2]
        return results


@TRANSFORMS.register_module()
class VideoFormat(BaseTransform):
    def transform(self, results):
        num_clips = results['num_clips']
        clip_len = results['clip_len']
        imgs = results['imgs']

        # [num_clips*clip_len, H, W, C]
        imgs = np.array(imgs)
        # [num_clips, clip_len, H, W, C]
        imgs = imgs.reshape((num_clips, clip_len) + imgs.shape[1:])
        # [num_clips, C, clip_len, H, W]
        imgs = imgs.transpose(0, 4, 1, 2, 3)

        results['imgs'] = imgs
        return results


@TRANSFORMS.register_module()
class VideoPack(BaseTransform):
    def __init__(self, meta_keys=('img_shape', 'num_clips', 'clip_len')):
        self.meta_keys = meta_keys

    def transform(self, results):
        packed_results = dict()
        inputs = to_tensor(results['imgs'])
        data_sample = ActionDataSample().set_gt_label(results['label'])
        metainfo = {k: results[k] for k in self.meta_keys if k in results}
        data_sample.set_metainfo(metainfo)
        packed_results['inputs'] = inputs
        packed_results['data_samples'] = data_sample
        return packed_results

import os.path as osp
from mmengine.dataset import Compose

pipeline_cfg = [
    dict(type='VideoInit'),
    dict(type='VideoSample', clip_len=16, num_clips=1, test_mode=False),
    dict(type='VideoDecode'),
    dict(type='VideoResize', r_size=256),
    dict(type='VideoCrop', c_size=224),
    dict(type='VideoFormat'),
    dict(type='VideoPack')
]

pipeline = Compose(pipeline_cfg)
data_prefix = 'data/kinetics400_tiny/train'
results = dict(filename=osp.join(data_prefix, 'D32_1gwq35E.mp4'), label=0)
packed_results = pipeline(results)

inputs = packed_results['inputs']
data_sample = packed_results['data_samples']

print('shape of the inputs: ', inputs.shape)

# 获取输入的信息
print('image_shape: ', data_sample.img_shape)
print('num_clips: ', data_sample.num_clips)
print('clip_len: ', data_sample.clip_len)

# 获取输入的标签
print('label: ', data_sample.gt_label)
print('**************************步骤2:构建一个数据集和数据加载器*****************************')
import os.path as osp
from mmengine.fileio import list_from_file
from mmengine.dataset import BaseDataset
from mmaction.registry import DATASETS


@DATASETS.register_module()
class DatasetZelda(BaseDataset):
    def __init__(self, ann_file, pipeline, data_root, data_prefix=dict(video=''),
                 test_mode=False, modality='RGB', **kwargs):
        self.modality = modality
        super(DatasetZelda, self).__init__(ann_file=ann_file, pipeline=pipeline, data_root=data_root,
                                           data_prefix=data_prefix, test_mode=test_mode,
                                           **kwargs)

    def load_data_list(self):
        data_list = []
        fin = list_from_file(self.ann_file)
        for line in fin:
            line_split = line.strip().split()
            filename, label = line_split
            label = int(label)
            filename = osp.join(self.data_prefix['video'], filename)
            data_list.append(dict(filename=filename, label=label))
        return data_list

    def get_data_info(self, idx: int) -> dict:
        data_info = super().get_data_info(idx)
        data_info['modality'] = self.modality
        return data_info

from mmaction.registry import DATASETS

train_pipeline_cfg = [
    dict(type='VideoInit'),
    dict(type='VideoSample', clip_len=16, num_clips=1, test_mode=False),
    dict(type='VideoDecode'),
    dict(type='VideoResize', r_size=256),
    dict(type='VideoCrop', c_size=224),
    dict(type='VideoFormat'),
    dict(type='VideoPack')
]

val_pipeline_cfg = [
    dict(type='VideoInit'),
    dict(type='VideoSample', clip_len=16, num_clips=5, test_mode=True),
    dict(type='VideoDecode'),
    dict(type='VideoResize', r_size=256),
    dict(type='VideoCrop', c_size=224),
    dict(type='VideoFormat'),
    dict(type='VideoPack')
]

train_dataset_cfg = dict(
    type='DatasetZelda',
    ann_file='kinetics_tiny_train_video.txt',
    pipeline=train_pipeline_cfg,
    data_root='data/kinetics400_tiny/',
    data_prefix=dict(video='train'))

val_dataset_cfg = dict(
    type='DatasetZelda',
    ann_file='kinetics_tiny_val_video.txt',
    pipeline=val_pipeline_cfg,
    data_root='data/kinetics400_tiny/',
    data_prefix=dict(video='val'))

train_dataset = DATASETS.build(train_dataset_cfg)

packed_results = train_dataset[0]

inputs = packed_results['inputs']
data_sample = packed_results['data_samples']

print('shape of the inputs: ', inputs.shape)

# 获取输入的信息
print('image_shape: ', data_sample.img_shape)
print('num_clips: ', data_sample.num_clips)
print('clip_len: ', data_sample.clip_len)

# 获取输入的标签
print('label: ', data_sample.gt_label)

from mmengine.runner import Runner

BATCH_SIZE = 2

train_dataloader_cfg = dict(
    batch_size=BATCH_SIZE,
    num_workers=0,
    persistent_workers=False,
    sampler=dict(type='DefaultSampler', shuffle=True),
    dataset=train_dataset_cfg)

val_dataloader_cfg = dict(
    batch_size=BATCH_SIZE,
    num_workers=0,
    persistent_workers=False,
    sampler=dict(type='DefaultSampler', shuffle=False),
    dataset=val_dataset_cfg)

train_data_loader = Runner.build_dataloader(dataloader=train_dataloader_cfg)
val_data_loader = Runner.build_dataloader(dataloader=val_dataloader_cfg)

batched_packed_results = next(iter(train_data_loader))

batched_inputs = batched_packed_results['inputs']
batched_data_sample = batched_packed_results['data_samples']

assert len(batched_inputs) == BATCH_SIZE
assert len(batched_data_sample) == BATCH_SIZE
print('**************************步骤3:构建一个识别器*****************************')
import torch
from mmengine.model import BaseDataPreprocessor, stack_batch
from mmaction.registry import MODELS


@MODELS.register_module()
class DataPreprocessorZelda(BaseDataPreprocessor):
    def __init__(self, mean, std):
        super().__init__()

        self.register_buffer(
            'mean',
            torch.tensor(mean, dtype=torch.float32).view(-1, 1, 1, 1),
            False)
        self.register_buffer(
            'std',
            torch.tensor(std, dtype=torch.float32).view(-1, 1, 1, 1),
            False)

    def forward(self, data, training=False):
        data = self.cast_data(data)
        inputs = data['inputs']
        batch_inputs = stack_batch(inputs)  # 批处理
        batch_inputs = (batch_inputs - self.mean) / self.std  # 归一化
        data['inputs'] = batch_inputs
        return data

from mmaction.registry import MODELS

data_preprocessor_cfg = dict(
    type='DataPreprocessorZelda',
    mean=[123.675, 116.28, 103.53],
    std=[58.395, 57.12, 57.375])

data_preprocessor = MODELS.build(data_preprocessor_cfg)

preprocessed_inputs = data_preprocessor(batched_packed_results)
print(preprocessed_inputs['inputs'].shape)

import torch
import torch.nn as nn
import torch.nn.functional as F
from mmengine.model import BaseModel, BaseModule, Sequential
from mmengine.structures import LabelData
from mmaction.registry import MODELS


@MODELS.register_module()
class BackBoneZelda(BaseModule):
    def __init__(self, init_cfg=None):
        if init_cfg is None:
            init_cfg = [dict(type='Kaiming', layer='Conv3d', mode='fan_out', nonlinearity="relu"),
                        dict(type='Constant', layer='BatchNorm3d', val=1, bias=0)]

        super(BackBoneZelda, self).__init__(init_cfg=init_cfg)

        self.conv1 = Sequential(nn.Conv3d(3, 64, kernel_size=(3, 7, 7),
                                          stride=(1, 2, 2), padding=(1, 3, 3)),
                                nn.BatchNorm3d(64), nn.ReLU())
        self.maxpool = nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2),
                                    padding=(0, 1, 1))

        self.conv = Sequential(nn.Conv3d(64, 128, kernel_size=3, stride=2, padding=1),
                               nn.BatchNorm3d(128), nn.ReLU())

    def forward(self, imgs):
        # imgs: [batch_size*num_views, 3, T, H, W]
        # features: [batch_size*num_views, 128, T/2, H//8, W//8]
        features = self.conv(self.maxpool(self.conv1(imgs)))
        return features


@MODELS.register_module()
class ClsHeadZelda(BaseModule):
    def __init__(self, num_classes, in_channels, dropout=0.5, average_clips='prob', init_cfg=None):
        if init_cfg is None:
            init_cfg = dict(type='Normal', layer='Linear', std=0.01)

        super(ClsHeadZelda, self).__init__(init_cfg=init_cfg)

        self.num_classes = num_classes
        self.in_channels = in_channels
        self.average_clips = average_clips

        if dropout != 0:
            self.dropout = nn.Dropout(dropout)
        else:
            self.dropout = None

        self.fc = nn.Linear(self.in_channels, self.num_classes)
        self.pool = nn.AdaptiveAvgPool3d(1)
        self.loss_fn = nn.CrossEntropyLoss()

    def forward(self, x):
        N, C, T, H, W = x.shape
        x = self.pool(x)
        x = x.view(N, C)
        assert x.shape[1] == self.in_channels

        if self.dropout is not None:
            x = self.dropout(x)

        cls_scores = self.fc(x)
        return cls_scores

    def loss(self, feats, data_samples):
        cls_scores = self(feats)
        labels = torch.stack([x.gt_label for x in data_samples])
        labels = labels.squeeze()

        if labels.shape == torch.Size([]):
            labels = labels.unsqueeze(0)

        loss_cls = self.loss_fn(cls_scores, labels)
        return dict(loss_cls=loss_cls)

    def predict(self, feats, data_samples):
        cls_scores = self(feats)
        num_views = cls_scores.shape[0] // len(data_samples)
        # assert num_views == data_samples[0].num_clips
        cls_scores = self.average_clip(cls_scores, num_views)

        for ds, sc in zip(data_samples, cls_scores):
            pred = LabelData(item=sc)
            ds.pred_scores = pred
        return data_samples

    def average_clip(self, cls_scores, num_views):
          if self.average_clips not in ['score', 'prob', None]:
            raise ValueError(f'{self.average_clips} is not supported. '
                             f'Currently supported ones are '
                             f'["score", "prob", None]')

          total_views = cls_scores.shape[0]
          cls_scores = cls_scores.view(total_views // num_views, num_views, -1)

          if self.average_clips is None:
              return cls_scores
          elif self.average_clips == 'prob':
              cls_scores = F.softmax(cls_scores, dim=2).mean(dim=1)
          elif self.average_clips == 'score':
              cls_scores = cls_scores.mean(dim=1)

          return cls_scores


@MODELS.register_module()
class RecognizerZelda(BaseModel):
    def __init__(self, backbone, cls_head, data_preprocessor):
        super().__init__(data_preprocessor=data_preprocessor)

        self.backbone = MODELS.build(backbone)
        self.cls_head = MODELS.build(cls_head)

    def extract_feat(self, inputs):
        inputs = inputs.view((-1, ) + inputs.shape[2:])
        return self.backbone(inputs)

    def loss(self, inputs, data_samples):
        feats = self.extract_feat(inputs)
        loss = self.cls_head.loss(feats, data_samples)
        return loss

    def predict(self, inputs, data_samples):
        feats = self.extract_feat(inputs)
        predictions = self.cls_head.predict(feats, data_samples)
        return predictions

    def forward(self, inputs, data_samples=None, mode='tensor'):
        if mode == 'tensor':
            return self.extract_feat(inputs)
        elif mode == 'loss':
            return self.loss(inputs, data_samples)
        elif mode == 'predict':
            return self.predict(inputs, data_samples)
        else:
            raise RuntimeError(f'Invalid mode: {mode}')

import torch
import copy
from mmaction.registry import MODELS

model_cfg = dict(
    type='RecognizerZelda',
    backbone=dict(type='BackBoneZelda'),
    cls_head=dict(
        type='ClsHeadZelda',
        num_classes=2,
        in_channels=128,
        average_clips='prob'),
    data_preprocessor = dict(
        type='DataPreprocessorZelda',
        mean=[123.675, 116.28, 103.53],
        std=[58.395, 57.12, 57.375]))

model = MODELS.build(model_cfg)

# 训练
model.train()
model.init_weights()
data_batch_train = copy.deepcopy(batched_packed_results)
data = model.data_preprocessor(data_batch_train, training=True)
loss = model(**data, mode='loss')
print('loss dict: ', loss)

# 验证
with torch.no_grad():
    model.eval()
    data_batch_test = copy.deepcopy(batched_packed_results)
    data = model.data_preprocessor(data_batch_test, training=False)
    predictions = model(**data, mode='predict')
here = (predictions)
print('Label of Sample[0]', predictions[0].gt_label)
print('----------------------------------------------------')
print('Label of Sample[0]', predictions[0].gt_label)
print('Scores of Sample[0]', predictions[0].pred_scores.item)
print('**************************步骤4:构建一个评估指标*****************************')
import copy
from collections import OrderedDict
from mmengine.evaluator import BaseMetric
from mmaction.evaluation import top_k_accuracy
from mmaction.registry import METRICS


@METRICS.register_module()
class AccuracyMetric(BaseMetric):
    def __init__(self, topk=(1, 5), collect_device='cpu', prefix='acc'):
        super().__init__(collect_device=collect_device, prefix=prefix)
        self.topk = topk

    def process(self, data_batch, data_samples):
        data_samples = copy.deepcopy(data_samples)
        for data_sample in data_samples:
            result = dict()
            scores = data_sample['pred_scores']['item']
            scores = np.array(scores)
            label = data_sample['gt_label'].item()
            result['scores'] = scores
            result['label'] = label
            self.results.append(result)

    def compute_metrics(self, results: list) -> dict:
        eval_results = OrderedDict()
        labels = [res['label'] for res in results]
        scores = [res['scores'] for res in results]
        topk_acc = top_k_accuracy(scores, labels, self.topk)
        for k, acc in zip(self.topk, topk_acc):
            eval_results[f'topk{k}'] = acc
        return eval_results

from mmaction.registry import METRICS

metric_cfg = dict(type='AccuracyMetric', topk=(1, 5))

metric = METRICS.build(metric_cfg)

data_samples = [d.to_dict() for d in predictions]

metric.process(batched_packed_results, data_samples)
acc = metric.compute_metrics(metric.results)
print(acc)
print('**************************步骤5:使用本地 PyTorch 训练和测试*****************************')
import torch.optim as optim
from mmengine import track_iter_progress
from tqdm import tqdm


device = 'cuda' # or 'cpu'
max_epochs = 10

optimizer = optim.Adam(model.parameters(), lr=0.01)

for epoch in range(max_epochs):
    model.train()
    losses = []
    task_num = len(train_data_loader)
    for data_batch in track_iter_progress((train_data_loader, task_num)):
        data = model.data_preprocessor(data_batch, training=True)
        loss_dict = model(**data, mode='loss')
        loss = loss_dict['loss_cls']

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        losses.append(loss.item())

    print(f'Epoch[{epoch}]: loss ', sum(losses) / len(train_data_loader))

    with torch.no_grad():
        model.eval()
        task_num = len(val_data_loader)
        for data_batch in track_iter_progress((val_data_loader,task_num)):
            data = model.data_preprocessor(data_batch, training=False)
            predictions = model(**data, mode='predict')
            data_samples = [d.to_dict() for d in predictions]
            metric.process(data_batch, data_samples)

        acc = metric.acc = metric.compute_metrics(metric.results)
        for name, topk in acc.items():
            print(f'{name}: ', topk)
print('**************************步骤6:使用 MMEngine 训练和测试(推荐)*****************************')
# from mmengine.runner import Runner
#
# train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=10, val_interval=1)
# val_cfg = dict(type='ValLoop')
#
# optim_wrapper = dict(optimizer=dict(type='Adam', lr=0.01))
#
# runner = Runner(model=model_cfg, work_dir='./work_dirs/guide',
#                 train_dataloader=train_dataloader_cfg,
#                 train_cfg=train_cfg,
#                 val_dataloader=val_dataloader_cfg,
#                 val_cfg=val_cfg,
#                 optim_wrapper=optim_wrapper,
#                 val_evaluator=[metric_cfg],
#                 default_scope='mmaction')
# runner.train()

用户指南

使用现有模型进行推理

MMAction2 提供了用于对给定视频进行推理的高级 Python API:

  • init_recognizer: 使用配置文件和权重文件初始化一个识别器

  • inference_recognizer: 对给定视频进行推理

下面是一个使用 Kinitics-400 预训练权重构建模型并对给定视频进行推理的示例

python 复制代码
from mmaction.apis import inference_recognizer, init_recognizer

config_path = 'configs/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x8-100e_kinetics400-rgb.py'
checkpoint_path = 'https://download.openmmlab.com/mmaction/v1.0/recognition/tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x8-100e_kinetics400-rgb/tsn_imagenet-pretrained-r50_8xb32-1x1x8-100e_kinetics400-rgb_20220906-2692d16c.pth' # 可以是本地路径
img_path = 'demo/demo.mp4'   # 您可以指定自己的图片路径

# 从配置文件和权重文件中构建模型
model = init_recognizer(config_path, checkpoint_path, device="cpu")  # device 可以是 'cuda:0'
# 对单个视频进行测试
result = inference_recognizer(model, img_path)
# result 是一个包含 pred_scores 的字典,概率最高的在pred_scores中索引为6
print(result)

学习配置文件

使用 Python 文件作为配置文件,可以在 $MMAction2/configs 目录下找到所有提供的配置文件,可以运行 python tools/analysis_tools/print_config.py /PATH/TO/CONFIG 来查看完整的配置文件。

通过脚本参数修改配置

在使用 tools/train.py 或 tools/test.py 提交作业时,您可以通过指定 --cfg-options 来原地修改配置。

  • 更新字典的配置键。

可以按照原始配置中字典键的顺序来指定配置选项。 例如,--cfg-options model.backbone.norm_eval=False 将模型骨干中的所有 BN 模块更改为 train 模式。

  • 更新配置列表中的键。

一些配置字典在配置文件中以列表形式组成。例如,训练流程 train_pipeline 通常是一个列表, 例如 [dict(type='SampleFrames'), ...]。如果您想要在流程中将 'SampleFrames' 更改为 'DenseSampleFrames', 您可以指定 --cfg-options train_pipeline.0.type=DenseSampleFrames。

  • 更新列表/元组的值。

如果要更新的值是列表或元组。例如,配置文件通常设置 model.data_preprocessor.mean=[123.675, 116.28, 103.53]。如果您想要 更改此键,您可以指定 --cfg-options model.data_preprocessor.mean="[128,128,128]"。请注意,引号 " 是支持列表/元组数据类型的必需内容。

配置文件结构

configs/base 下有 3 种基本组件类型,即 models、schedules 和 default_runtime。 许多方法只需要一个模型、一个训练计划和一个默认运行时组件就可以轻松构建,如 TSN、I3D、SlowOnly 等。 由 base 组件组成的配置文件被称为 primitive。

对于同一文件夹下的所有配置文件,建议只有一个 primitive 配置文件。其他所有配置文件都应该继承自 primitive 配置文件。这样,继承级别的最大值为 3。

为了方便理解,我们建议贡献者继承现有方法。 例如,如果基于 TSN 进行了一些修改,用户可以首先通过指定 base = ../tsn/tsn_imagenet-pretrained-r50_8xb32-1x1x3-100e_kinetics400-rgb.py 来继承基本的 TSN 结构,然后在配置文件中修改必要的字段。

如果您正在构建一个与任何现有方法的结构不共享的全新方法,可以在 configs/TASK 下创建一个文件夹

配置文件命名约定

配置文件名分为几个部分,不同部分逻辑上用下划线 '' 连接,同一部分的设置用破折号 '-' 连接 {算法信息} {模块信息}{训练信息}{数据信息}.py {xxx} 是必填字段,[yyy] 是可选字段

  • {算法信息}: {模型}: 模型类型,例如 tsn、i3d、swin、vit 等。 [模型设置]: 某些模型的特定设置,例如 base、p16、w877 等。

  • {模块信息}: [预训练信息]: 预训练信息,例如 kinetics400-pretrained、in1k-pre 等。 {骨干网络}: 骨干网络类型,例如 r50(ResNet-50)等。 [骨干网络设置]: 某些骨干网络的特定设置,例如 nl-dot-product、bnfrozen、nopool 等。

  • {训练信息}: {gpu x batch_per_gpu]}: GPU 和每个 GPU 上的样本数。 {pipeline设置}: 帧采样设置,例如 dense、{clip_len}x{frame_interval}x{num_clips}、u48 等。 {schedule}: 训练计划,例如 coslr-20e。

  • {数据信息}: {数据集}: 数据集名称,例如 kinetics400、mmit 等。 {模态}: 数据模态,例如 rgb、flow、keypoint-2d 等。

python 复制代码
# 模型设置
model = dict(  # 模型的配置
    type='Recognizer2D',  # 识别器的类名
    backbone=dict(  # 骨干网络的配置
        type='ResNet',  # 骨干网络的名称
        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),  # 一致性模块的配置
        dropout_ratio=0.4,  # dropout 层中的概率
        init_std=0.01, # 线性层初始化的标准差值
        average_clips='prob'),  # 平均多个剪辑结果的方法
    data_preprocessor=dict(  # 数据预处理器的配置
        type='ActionDataPreprocessor',  # 数据预处理器的名称
        mean=[123.675, 116.28, 103.53],  # 不同通道的均值用于归一化
        std=[58.395, 57.12, 57.375],  # 不同通道的标准差用于归一化
        format_shape='NCHW'),  # 最终图像形状的格式
    # 模型训练和测试设置
    train_cfg=None,  # TSN 的训练超参数的配置
    test_cfg=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'  # 用于测试的注释文件的路径

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(  # FormatShape 的配置
        type='FormatShape',  # 格式化形状的流程,将最终图像形状格式化为给定的 input_format
        input_format='NCHW'),  # 最终图像形状的格式
    dict(type='PackActionInputs')  # PackActionInputs 的配置
]
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(  # FormatShape 的配置
        type='FormatShape',  # 格式化形状的流程,将最终图像形状格式化为给定的 input_format
        input_format='NCHW'),  # 最终图像形状的格式
    dict(type='PackActionInputs')  # PackActionInputs 的配置
]
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',  # 十次裁剪的流程,从图像中裁剪十个区域
        crop_size=224),  # 要裁剪的图像大小
    dict(  # Flip 的配置
        type='Flip',  # 翻转的流程
        flip_ratio=0),  # 实施翻转的概率
    dict(  # FormatShape 的配置
        type='FormatShape',  # 格式化形状的流程,将最终图像形状格式化为给定的 input_format
        input_format='NCHW'),  # 最终图像形状的格式
    dict(type='PackActionInputs')  # PackActionInputs 的配置
]

train_dataloader = dict(  # 训练数据加载器的配置
    batch_size=32,  # 训练时每个单个 GPU 的批量大小
    num_workers=8,  # 训练时每个单个 GPU 的数据预取进程数
    persistent_workers=True,  # 如果为 `True`,则数据加载器在一个 epoch 结束后不会关闭工作进程,这可以加速训练速度
    sampler=dict(
        type='DefaultSampler',  # 支持分布式和非分布式训练的 DefaultSampler。参考 https://github.com/open-mmlab/mmengine/blob/main/mmengine/dataset/sampler.py
        shuffle=True),  # 每个 epoch 随机打乱训练数据
    dataset=dict(  # 训练数据集的配置
        type=dataset_type,
        ann_file=ann_file_train,  # 注释文件的路径
        data_prefix=dict(img=data_root),  # 帧路径的前缀
        pipeline=train_pipeline))
val_dataloader = dict(  # 验证数据加载器的配置
    batch_size=1,  # 验证时每个单个 GPU 的批量大小
    num_workers=8,  # 验证时每个单个 GPU 的数据预取进程数
    persistent_workers=True,  # 如果为 `True`,则数据加载器在一个 epoch 结束后不会关闭工作进程
    sampler=dict(
        type='DefaultSampler',
        shuffle=False),  # 验证和测试时不进行随机打乱
    dataset=dict(  # 验证数据集的配置
        type=dataset_type,
        ann_file=ann_file_val,  # 注释文件的路径
        data_prefix=dict(img=data_root_val),  # 帧路径的前缀
        pipeline=val_pipeline,
        test_mode=True))
test_dataloader = dict(  # 测试数据加载器的配置
    batch_size=32,  # 测试时每个单个 GPU 的批量大小
    num_workers=8,  # 测试时每个单个 GPU 的数据预取进程数
    persistent_workers=True,  # 如果为 `True`,则数据加载器在一个 epoch 结束后不会关闭工作进程
    sampler=dict(
        type='DefaultSampler',
        shuffle=False),  # 验证和测试时不进行随机打乱
    dataset=dict(  # 测试数据集的配置
        type=dataset_type,
        ann_file=ann_file_val,  # 注释文件的路径
        data_prefix=dict(img=data_root_val),  # 帧路径的前缀
        pipeline=test_pipeline,
        test_mode=True))

# 评估设置
val_evaluator = dict(type='AccMetric')  # 验证评估器的配置
test_evaluator = val_evaluator  # 测试评估器的配置

train_cfg = dict(  # 训练循环的配置
    type='EpochBasedTrainLoop',  # 训练循环的名称
    max_epochs=100,  # 总的训练周期数
    val_begin=1,  # 开始验证的训练周期
    val_interval=1)  # 验证间隔
val_cfg = dict(  # 验证循环的配置
    type='ValLoop')  # 验证循环的名称
test_cfg = dict( # 测试循环的配置
    type='TestLoop')  # 测试循环的名称

# 学习策略
param_scheduler = [  # 更新优化器参数的学习率测率,支持字典或列表
    dict(type='MultiStepLR',  # 达到一个里程碑时衰减学习率
        begin=0,  # 开始更新学习率的步骤
        end=100,  # 结束更新学习率的步骤
        by_epoch=True,  # 是否按 epoch 更新学习率
        milestones=[40, 80],  # 衰减学习率的步骤
        gamma=0.1)]  # 学习率衰减的乘法因子

# 优化器
optim_wrapper = dict(  # 优化器包装器的配置
    type='OptimWrapper',  # 优化器包装器的名称,切换到 AmpOptimWrapper 可以启用混合精度训练
    optimizer=dict(  # 优化器的配置。支持 PyTorch 中的各种优化器。参考 https://pytorch.org/docs/stable/optim.html#algorithms
        type='SGD',  # 优化器的名称
        lr=0.01,  # 学习率
        momentum=0.9,  # 动量因子
        weight_decay=0.0001),  # 权重衰减
    clip_grad=dict(max_norm=40, norm_type=2))  # 梯度裁剪的配置

# 运行时设置
default_scope = 'mmaction'  # 用于查找模块的默认注册表作用域。参考 https://mmengine.readthedocs.io/en/latest/tutorials/registry.html
default_hooks = dict(  # 执行默认操作的钩子,如更新模型参数和保存权重。
    runtime_info=dict(type='RuntimeInfoHook'),  # 将运行时信息更新到消息中心的钩子
    timer=dict(type='IterTimerHook'),  # 用于记录迭代过程中花费的时间的日志记录器
    logger=dict(
        type='LoggerHook',  # 用于记录训练/验证/测试阶段的日志记录器
        interval=20,  # 打印日志的间隔
        ignore_last=False), # 忽略每个 epoch 中最后几个迭代的日志
    param_scheduler=dict(type='ParamSchedulerHook'),  # 更新优化器中某些超参数的钩子
    checkpoint=dict(
        type='CheckpointHook',  # 定期保存权重的钩子
        interval=3,  # 保存的周期
        save_best='auto',  # 用于评估最佳权重的指标
        max_keep_ckpts=3),  # 保留的最大权重文件数量
    sampler_seed=dict(type='DistSamplerSeedHook'),  # 用于分布式训练的数据加载采样器
    sync_buffers=dict(type='SyncBuffersHook'))  # 在每个 epoch 结束时同步模型缓冲区

env_cfg = dict(  # 设置环境的字典
    cudnn_benchmark=False,  # 是否启用 cudnn benchmark
    mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), # 设置多进程的参数
    dist_cfg=dict(backend='nccl')) # 设置分布式环境的参数,也可以设置端口号

log_processor = dict(
    type='LogProcessor',  # 用于格式化日志信息的日志处理器
    window_size=20,  # 默认的平滑间隔
    by_epoch=True)  # 是否使用 epoch 类型格式化日志
vis_backends = [  # 可视化后端的列表
    dict(type='LocalVisBackend')]  # 本地可视化后端
visualizer = dict(  # 可视化器的配置
    type='ActionVisualizer',  # 可视化器的名称
    vis_backends=vis_backends)
log_level = 'INFO'  # 日志记录的级别
load_from = None  # 从给定路径加载模型权重作为预训练模型。这不会恢复训练。
resume = False  # 是否从 `load_from` 中定义的权重恢复。如果 `load_from` 为 None,则会从 `work_dir` 中恢复最新的权重。

数据集支持

点击跳转官方文档

使用CLI下载

使用CLI下载

如果openxlab login出错,就使用openxlab config,.openxlab\config中键入ak和sk

下载整个数据集

powershell 复制代码
openxlab dataset get --dataset-repo username/repo-name
                     --target-path /path/to/local/folder

openxlab dataset get -r username/repo-name 
                     -t /path/to/local/folder

下载数据集的某一个文件

powershell 复制代码
openxlab dataset download --dataset-repo username/repo-name
                          --source-path /train/file
                          --target-path /path/to/local/folder

openxlab dataset download -r username/repo-name
                          -s /train/file
                          -t /path/to/local/folder

其中:

参数 缩写 是否必填 参数类型 参数说明 示例
dataset-repo -r String 数据集仓库的地址,由 username/repo_name 组成 zhangsan/repo-name
source-path -s String 对应数据集仓库下文件的相对路径 -s /train/file
target-path -t String 下载仓库指定的本地路径 --target-path /path/to/local/folder

使用SDK下载

下载整个数据集

powershell 复制代码
from openxlab.dataset import get
get(dataset_repo='username/repo_name', target_path='/path/to/local/folder')

下载数据集的某一个文件

powershell 复制代码
from openxlab.dataset import download
download(dataset_repo='username/repo_name', source_path='/train/file', target_path='/path/to/local/folder')

其中:

参数 缩写 是否必填 参数类型 参数说明 示例
dataset _repo -r String 数据集仓库的地址,由 username/repo_name 组成 zhangsan/repo-name
source_path -s String 对应数据集仓库下文件的相对路径 -s /path/to/local/folder
target_path -t String 下载仓库指定的本地路径 --target-path /path/to/local/folder
相关推荐
大咖分享课26 分钟前
多租户系统中的安全隔离机制设计
人工智能·安全·安全隔离
九章云极AladdinEdu42 分钟前
GitHub新手生存指南:AI项目版本控制与协作实战
人工智能·pytorch·opencv·机器学习·github·gpu算力
霜绛1 小时前
机器学习笔记(三)——决策树、随机森林
人工智能·笔记·学习·决策树·随机森林·机器学习
PPIO派欧云1 小时前
PPIO上线阿里旗舰推理模型Qwen3-235B-A22B-Thinking-2507
人工智能·语言模型
赴3352 小时前
Numpy 库 矩阵数学运算,点积,文件读取和保存等
人工智能·算法·numpy·random·dot
ManageEngineITSM2 小时前
从混乱到秩序:IT服务管理如何重塑企业运营效率
大数据·人工智能·程序人生·职场和发展·itsm
优宁维生物2 小时前
血液样本的分类与应用
人工智能·分类·数据挖掘
Blossom.1183 小时前
基于深度学习的图像分类:使用DenseNet实现高效分类
人工智能·深度学习·目标检测·机器学习·分类·数据挖掘·迁移学习
慧星云3 小时前
慧星云新增大模型服务 :多款大模型轻松调用
人工智能
蓝蜂物联网3 小时前
边缘计算网关与 EMCP 物联网云平台:无缝协作,共筑智能生态
人工智能·物联网·边缘计算