掘金 人工智能 21小时前
MMAction2-1.2.0文档
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文档详细介绍了 MMAction2 视频动作识别框架的安装、快速运行和数据集准备、配置修改、训练与测试的完整流程。从环境搭建到模型训练,提供了一系列清晰的步骤和代码示例。通过对 Kinetics400 小数据集的配置和训练,帮助用户快速掌握 MMAction2 的核心功能,为进一步的视频动作识别研究和应用奠定基础。

📦 **环境搭建与安装**: 指南首先提供了详细的 MMAction2 安装步骤,包括使用 Conda 创建环境、安装 PyTorch、MMCV 和 OpenMIM,以及通过 `mim install` 命令来安装 MMAction2 及其依赖。同时,也指出了针对不同操作系统(Linux/Windows)在安装 MMCV 时需要注意的 CUDA 版本和后端配置。

🚀 **快速运行与推理**: 在安装完成后,用户可以立即进行视频推理的演示。通过 `demo_inferencer.py` 脚本,加载预训练模型对示例视频进行动作识别,并展示了推理结果的格式,让用户快速体验 MMAction2 的核心功能。

📊 **数据集准备与配置修改**: 为了方便用户进行训练,文档详细介绍了如何下载和准备 Kinetics400 tiny 数据集,并指导用户如何修改配置文件,包括数据根目录、训练/验证集标注文件、批大小、训练轮数、验证间隔以及学习率衰减策略等。此外,还强调了根据实际类别数调整模型配置的重要性。

🏋️ **模型训练与测试**: 文档清晰地阐述了如何启动模型训练,并指出训练产生的权重和日志文件的保存位置。同时,也提供了在测试阶段进行模型评估的步骤,包括修改预测分数和评估指标的获取方式,以及如何正确地迭代数据加载器进行测试。

💡 **自定义模块与框架设计**: 文档还展示了如何通过注册模块的方式自定义数据预处理流程(如 `DataPreprocessorZelda`)和模型结构(如 `BackBoneZelda` 和 `ClsHeadZelda`),以及如何将这些自定义模块集成到 `RecognizerZelda` 模型中。这为用户根据自身需求扩展 MMAction2 框架提供了指导。

新手入门

安装

pythontorchtorchaudiotorchvisioncudammcv
3.8.102.0.02.0.10.15.112.1.12.1.0
# 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.1pip install -U openmimmim 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.htmlgit clone https://github.com/open-mmlab/mmaction2.gitcd mmaction2pip install -v -e .# "-v" 表示输出更多安装相关的信息# "-e" 表示以可编辑形式安装,这样可以在不重新安装的情况下,让本地修改直接生效。

点击跳转MMCV官方安装教程

快速运行

推理

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': [[...]]}]}

准备数据集

# 下载官方预先准备好的 kinetics400_tiny.zip ,并将其解压到 MMAction2 根目录下的 data/ 目录wget https://download.openmmlab.com/mmaction/kinetics400_tiny.zipmkdir -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),只需要在其基础上进行一些修改。

# 修改数据集# 打开 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 中对应的关键字,如下所示生效。# 设置训练批大小为 4train_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中。

训练

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:构建一个识别器

# 修改此处 predictions[0].pred_score -> predictions[0].pred_scores.itemprint('Scores of Sample[0]', predictions[0].pred_scores.item)

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

# 修改此处 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 训练和测试

'''修改此处 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)):

修改完的完整代码

from mmaction.utils import register_all_modulesregister_all_modules(init_default_scope=True)print('**************************步骤0:准备数据*****************************')print('**************************步骤1:构建一个数据流水线*****************************')import mmcvimport decordimport numpy as npfrom mmcv.transforms import TRANSFORMS, BaseTransform, to_tensorfrom 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_resultsimport os.path as ospfrom mmengine.dataset import Composepipeline_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 ospfrom mmengine.fileio import list_from_filefrom mmengine.dataset import BaseDatasetfrom 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_infofrom mmaction.registry import DATASETStrain_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 RunnerBATCH_SIZE = 2train_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_SIZEassert len(batched_data_sample) == BATCH_SIZEprint('**************************步骤3:构建一个识别器*****************************')import torchfrom mmengine.model import BaseDataPreprocessor, stack_batchfrom 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 datafrom mmaction.registry import MODELSdata_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 torchimport torch.nn as nnimport torch.nn.functional as Ffrom mmengine.model import BaseModel, BaseModule, Sequentialfrom mmengine.structures import LabelDatafrom 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 torchimport copyfrom mmaction.registry import MODELSmodel_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 copyfrom collections import OrderedDictfrom mmengine.evaluator import BaseMetricfrom mmaction.evaluation import top_k_accuracyfrom 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_resultsfrom mmaction.registry import METRICSmetric_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 optimfrom mmengine import track_iter_progressfrom tqdm import tqdmdevice = 'cuda' # or 'cpu'max_epochs = 10optimizer = 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:

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

from mmaction.apis import inference_recognizer, init_recognizerconfig_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中索引为6print(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] 是可选字段

# 模型设置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.htmldefault_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

下载整个数据集

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

下载数据集的某一个文件

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

其中:

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

使用SDK下载

下载整个数据集

from openxlab.dataset import getget(dataset_repo='username/repo_name', target_path='/path/to/local/folder')

下载数据集的某一个文件

from openxlab.dataset import downloaddownload(dataset_repo='username/repo_name', source_path='/train/file', target_path='/path/to/local/folder')

其中:

参数缩写是否必填参数类型参数说明示例
dataset_repo-rString数据集仓库的地址,由 username/repo_name 组成zhangsan/repo-name
source_path-sString对应数据集仓库下文件的相对路径-s /path/to/local/folder
target_path-tString下载仓库指定的本地路径--target-path /path/to/local/folder

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

MMAction2 视频动作识别 PyTorch 深度学习 计算机视觉
相关文章