MindSpore实现X3D视频分类网络

X3D

X3D是一篇发表在CVPR2020上的关于视频动作分类的文章

原文链接

Code

Gitee

Github

NoteBook

算法原理

X3D的工作受机器学习中特征选择方法的启发,采用一个简单的逐步拓展网络的方法,以X2D图像分类模型为基础,分别在宽度、深度、帧率、帧数以及分辨率等维度逐步进行拓展,从2D空间拓展为3D时空域。每一次只在一个维度上进行拓展,并在计算量和精度上进行权衡,选取最佳的拓展方式。

image

作者对比了图像分类网络的发展史,这些图像分类模型经历了对深度、分辨率、通道宽度等维度的探索,但视频分类模型只是简单的对时间维度进行扩张。因此作者提出了对不同维度改进的思考。

3D网络最佳的时间采样策略是什么?长的时间序列和较为稀疏的采样是否优于短时间内的稠密采样?(采样帧率)

是否需要一个更好的空间分辨率?目前的工作都为提高效率而使用低分辨率。是否存在一个最大空间分辨率导致性能饱和?(空间分辨率)

更快的帧率+更“瘦”的模型好亦或更慢的帧率+更“宽”的模型好?也即slow分支和fast分支哪种的结构更好?又或者存在一个二者的中间结构更好?(帧率与宽度)

当增加网络宽度时,是增加全局的宽度好还是增加bottleneck的宽度好?(宽度,inverted bottlenetck结构的借鉴)

网络变深的同时,是否应该增加输入的时空分辨率以保证感受野大小足够大?又或者应该增大不同的维度?(深度与时空分辨率)

image

X3D整体网络结构如上,卷积核的维度表示为{T×S^2 , C }。X3D通过6个轴[图片上传失败...(image-df0f9-1679574532512)]

来对X2D进行拓展,X2D在这6个轴上都为1。

拓张维度:

  1. X-Fast:采样帧间隔 \gamma_T
  2. X-Temporal:采样帧数 \gamma_t
  3. X-Spatial:空间分辨率 \gamma_{s}
  4. X-Depth:网络深度 \gamma_{d}
  5. X-Width:网络宽度 \gamma_{w}
  6. X-Bottelneck:bottleneck宽度 \gamma_{b}

Forward expansion

前向拓张是给定复杂度,逐步逐维度进行拓张。

首先给定两个指标,一个是衡量当前扩张因子X好坏的J(X),该指标得分越高,拓展因子越好,得分越低,拓展因子越差,这对应的是模型的准确率。第二个是复杂度评判因子C(X),对应的是网络所需的浮点操作计算量,那么目标即为在给定复杂度C(X)=c的条件下,使得J(X)最大的扩张因子。

在网络尝试寻找最佳的拓展因子时,每一步只扩张一个维度,其他维度保持不变,而每一步最好的扩张因子被保留,接着进行下一步扩张。即在初始阶段,模型为X2D,对应着一个计算复杂度,然后给定一个目标复杂度,模型要通过每次改变一个因子,然后一步步变换到目标复杂度。且每一次改变所对应的改变量也是定义好的,即让当前的模型的复杂度变成两倍。再者,每一步的扩张是渐进式的,也即复杂度约2倍增长。这种方法可以看成是坐标下降法的特殊形式,扩张2倍的各维度操作具体如下:

  1. X-Fast: \gamma_t = 2\gamma_t
  2. X-Temporal: \gamma_t = 2\gamma_t
  3. X-Spatial: \gamma_{s} = \sqrt{2}\gamma_{s}
  4. X-Depth: \gamma_{d} = 2.2\gamma_{d}
  5. X-Width: \gamma_{w} = 2\gamma_{w}
  6. X-Bottelneck: \gamma_{b} = 2.25\gamma_{b}

Backward contraction

后向收缩是在超过复杂度时,进行回溯收缩。

由于前向扩展只在离散步骤中产生模型,如果目标复杂度被前向扩展步骤超过,他们执行后向收缩步骤以满足所需的目标复杂度。此收缩被实现为上一次展开的简单缩减,以便与目标相匹配。例如,如果最后一步将帧率提高了两倍,那么他们就会向后收缩将帧率降低到一个小于2的倍数,以大致匹配所需的目标复杂度。

渐进式拓张

image

扩张任意一个维度都增加了准确率,验证了最初的想法。

第一步扩张的不是时间维度,而是bottleneck宽度,这验证了MobileNetV2中的倒置残差结构,作者认为原因可能是这些层使用了channel-wise卷积十分轻量,因此首先扩张这个维度比较economical。且不同维度准确率变化很大,扩张bottleneck宽度达到了55.0%,而扩张深度只有51.3%。

第二步扩张的为帧数(因为最初只有单帧,因此扩展采样帧间隔和帧数是等同的),这也是我们认为“最应该在第一步扩张的维度”,因为者提供更多的时间信息。

第三步扩张的为空间分辨率,紧接着第四步为深度,接着是时间分辨率(帧率)和输入长度(帧间隔和帧数),然后是两次空间分辨率扩张,第十步再次扩张深度,这符合直观的想法,扩张深度会扩张滤波器感受野的大小。

值得注意的是,尽管模型一开始宽度比较小,但直到第十一步,模型才开始扩张全局的宽度,这使得X3D很像SlowFast的fast分支设计(时空分辨率很大但宽度很小),最后图里没显示扩张的两步为帧间隔和深度。

结果

image

环境准备

git clone https://gitee.com/yanlq46462828/zjut_mindvideo.git

 cd zjut_mindvideo

# Please first install mindspore according to instructions on the official website: https://www.mindspore.cn/install

pip install -r requirements.txt

pip install -e .

训练流程

from mindspore import nn
from mindspore.train import Model
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor
from mindspore.nn.loss import SoftmaxCrossEntropyWithLogits
from mindspore.nn.metrics import Accuracy
from msvideo.utils.check_param import Validator,Rel

数据集加载

通过基于VideoDataset编写的Kinetic400类来加载kinetic400数据集。


from msvideo.data.kinetics400 import Kinetic400

dataset = Kinetic400(path='/home/publicfile/kinetics-400',
                    split="train",
                    seq=16,
                    seq_mode='interval',
                    num_parallel_workers=4,
                    shuffle=True,
                    batch_size=16,
                    repeat_num=1,
                    frame_interval=5)
ckpt_save_dir = './x3d'

数据处理

用VideoShortEdgeResize根据短边来进行Resize,再用VideoRandomCrop对Resize后的视频进行随机裁剪,再用VideoRandomHorizontalFlip根据概率对视频进行水平翻转,通过VideoRescale对视频进行缩放,利用VideoReOrder对维度进行变换,再用VideoNormalize进行归一化处理。

from msvideo.data.transforms import VideoRandomCrop, VideoRandomHorizontalFlip, VideoRescale
from msvideo.data.transforms import VideoNormalize, VideoShortEdgeResize, VideoReOrder

transforms = [VideoShortEdgeResize((256)),
              VideoRandomCrop([224, 224]),
              VideoRandomHorizontalFlip(0.5),
              VideoRescale(shift=0),
              VideoReOrder((3, 0, 1, 2)),
              VideoNormalize([0.45, 0.45, 0.45], [0.225, 0.225, 0.225])]
dataset.transform = transforms
dataset_train = dataset.run()
Validator.check_int(dataset_train.get_dataset_size(), 0, Rel.GT)
step_size = dataset_train.get_dataset_size()

网络构建

X3D包含有多个子模型,通过调用X3D_M、X3D_S、X3D_XS、X3D_L来构建不同的模型。X3D模型主要由ResNetX3D和X3DHead两大部分构成。

ResNetX3D继承了ResNet3D,并在这基础上进行了修改。ResNetX3D的第一个模块是由两个3D卷积层以及batchnorm和relu构成的,第一个3D卷积层是空间维度的卷积,输入的通道数为3,输出的通道数是24,kernel大小为(1, 3, 3),stride为(1, 2, 2),第二个3D卷积层是时间维度的卷积,输入和输出通道均为24,kernel大小为(5, 1, 1)。ResNetX3D的后续模块是4个ResStage,每个ResStage中又含有不同数量的ResBlock。在ResBlock中,主要由下采样模块和Transform模块构成,下采样模块主要用于缩小输入的H和W的大小,Transform模块中含有多个conv模块来进行通道数量的变换,并引入了SE通道注意力机制和Swish非线性激活函数。而ResBlock的数量是由模型深度所决定的,每种模型所含有的ResBlock数量各不相同,以X3D-M为例,4个ResStage中所含有的ResBlock数量分别为3、5、11、7,在第一个ResStage中输入通道和输出通道都是24,中间通道是54,重复3次,在第二个ResStage中输入通道是24,输出通道是48,中间通道为108,重复5次,在第三个ResStage中输入通道是48,输出通道是96,中间通道为216,重复11次,在最后一个ResStage中,输入通道为96,输出通道192,中间通道432,重复7次。

X3Dhead是一个用于动作分类任务的Head,主要由3D平均池化层、3D卷积层、ReLU和线性层构成。X3DHead对于输入的特征,先将其变换为2048维的特征向量,再由线性层将其变换到类别数量。

from msvideo.models.x3d import x3d_m,x3d_s,x3d_xs,x3d_l
network = x3d_m(num_classes=400,
                dropout_rate=0.5,
                depth_factor=2.2,
                num_frames=16,
                train_crop_size=224)
network_x3d_s = x3d_s(num_classes = 400,
                      dropout_rate = 0.5,
                      depth_factor = 2.2,
                      num_frames = 13,
                      train_crop_size = 160,
                      eval_with_clips = False)
network_x3d_xs = x3d_xs(num_classes = 400,
                        dropout_rate = 0.5,
                        depth_factor = 2.2,
                        num_frames = 4,
                        train_crop_size = 160,
                        eval_with_clips = False)
network_x3d_l = x3d_l(num_classes = 400,
                      dropout_rate = 0.5,
                      depth_factor = 5.0,
                      num_frames = 16,
                      train_crop_size = 312,
                      eval_with_clips = False)

设置学习率、优化器和损失函数

from msvideo.schedule.lr_schedule import warmup_cosine_annealing_lr_v1

learning_rate = warmup_cosine_annealing_lr_v1(lr=0.0125,
                                            steps_per_epoch=step_size,
                                            warmup_epochs=35,
                                            max_epoch=100,
                                            t_max=100,
                                            eta_min=0)

network_opt = nn.SGD(network.trainable_params(),
                    learning_rate,
                    momentum=0.9,
                    weight_decay=0.00005)

network_loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")

ckpt_config = CheckpointConfig(
        save_checkpoint_steps=step_size,
        keep_checkpoint_max=10)
ckpt_callback = ModelCheckpoint(prefix='x3d_kinetics400',
                                directory=ckpt_save_dir,
                                config=ckpt_config)

初始化模型

# Init the model.
model = Model(network,
              loss_fn=network_loss,
              optimizer=network_opt,
              metrics={"Accuracy": Accuracy()})
# Begin to train.
print('[Start training `{}`]'.format('x3d_kinetics400'))
print("=" * 80)
model.train(100,
            dataset_train,
            callbacks=[ckpt_callback, LossMonitor()],
            dataset_sink_mode=False)
print('[End of training `{}`]'.format('x3d_kinetics400'))

部分结果


[WARNING] ME(721261:140224885696320,MainProcess):2023-02-28-03:15:02.447.555 [mindspore/dataset/engine/datasets_user_defined.py:766] GeneratorDataset's num_parallel_workers: 4 is too large which may cause a lot of memory occupation (>85%) or out of memory(OOM) during multiprocessing. Therefore, it is recommended to reduce num_parallel_workers to 1 or smaller.
[Start training `x3d_kinetics400`]
================================================================================
[WARNING] ME(721261:140224885696320,MainProcess):2023-02-28-03:15:02.899.032 [mindspore/dataset/core/validator_helpers.py:804] 'Compose' from mindspore.dataset.transforms.py_transforms is deprecated from version 1.8 and will be removed in a future version. Use 'Compose' from mindspore.dataset.transforms instead.
[WARNING] ME(721261:140224885696320,MainProcess):2023-02-28-03:15:02.903.305 [mindspore/dataset/engine/datasets_user_defined.py:766] GeneratorDataset's num_parallel_workers: 4 is too large which may cause a lot of memory occupation (>85%) or out of memory(OOM) during multiprocessing. Therefore, it is recommended to reduce num_parallel_workers to 1 or smaller.
[WARNING] ME(721261:140224885696320,MainProcess):2023-02-28-03:15:03.334.464 [mindspore/dataset/core/validator_helpers.py:804] 'Compose' from mindspore.dataset.transforms.py_transforms is deprecated from version 1.8 and will be removed in a future version. Use 'Compose' from mindspore.dataset.transforms instead.
epoch: 1 step: 1, loss is 5.99898624420166
epoch: 1 step: 2, loss is 5.985357761383057
epoch: 1 step: 3, loss is 5.989644527435303
epoch: 1 step: 4, loss is 5.99155330657959
epoch: 1 step: 5, loss is 5.987839698791504
epoch: 1 step: 6, loss is 5.990924835205078
epoch: 1 step: 7, loss is 5.9942498207092285
epoch: 1 step: 8, loss is 6.004180908203125
epoch: 1 step: 9, loss is 5.980659008026123
epoch: 1 step: 10, loss is 5.995561122894287
......

评估流程

以X3D-M为例

from mindspore import context
from mindspore.train.callback import Callback

class PrintEvalStep(Callback):
    """ print eval step """
    def step_end(self, run_context):
        """ eval step """
        cb_params = run_context.original_args()
        print("eval: {}/{}".format(cb_params.cur_step_num, cb_params.batch_num))
context.set_context(mode=context.GRAPH_MODE, device_target="GPU", device_id=1)

构建测试用数据集,并作相应的数据增强

from msvideo.data.kinetics400 import Kinetic400

dataset_eval = Kinetic400(path="/home/publicfile/kinetics-400",
                          split="val",
                          seq=16,
                          seq_mode='interval',
                          num_parallel_workers=8,
                          shuffle=False,
                          batch_size=16,
                          repeat_num=1,
                          frame_interval=5,
                          num_clips=10)

from msvideo.data.transforms import VideoReOrder, VideoRescale, VideoNormalize
from msvideo.data.transforms import VideoCenterCrop, VideoShortEdgeResize

transforms = [VideoShortEdgeResize(size=256),
              VideoCenterCrop([256, 256]),
              VideoRescale(shift=0),
              VideoReOrder((3, 0, 1, 2)),
              VideoNormalize([0.45, 0.45, 0.45], [0.225, 0.225, 0.225])]

dataset_eval.transform = transforms
dataset_eval = dataset_eval.run()

构建网络

from mindspore import nn
from mindspore.train import Model
from mindspore.nn.loss import SoftmaxCrossEntropyWithLogits
from mindspore import load_checkpoint, load_param_into_net
from msvideo.models.x3d import x3d_m

network = x3d_m(num_classes=400,
                eval_with_clips=True)

定义损失函数并加载预训练网络

# Define loss function.
network_loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")
# Load pretrained model.
param_dict = load_checkpoint(ckpt_file_name='/home/shr/resources/pretrianed_models/x3d_m_kinetics400.ckpt')
load_param_into_net(network, param_dict)

设置评估参数并初始化网络

# Define eval_metrics.
eval_metrics = {'Loss': nn.Loss(),
                'Top_1_Accuracy': nn.Top1CategoricalAccuracy(),
                'Top_5_Accuracy': nn.Top5CategoricalAccuracy()}
print_cb = PrintEvalStep()

# Init the model.
model = Model(network, loss_fn=network_loss, metrics=eval_metrics)

开始测试

# Begin to eval.
print('[Start eval `{}`]'.format('x3d_kinetics400'))
result = model.eval(dataset_eval,
                    callbacks=[print_cb],
                    dataset_sink_mode=False)
print(result)

测试结果

[WARNING] ME(139331:140018904409920,MainProcess):2023-03-13-08:00:16.289.382 [mindspore/train/model.py:1077] For PrintEvalStep callback, {'step_end'} methods may not be supported in later version, Use methods prefixed with 'on_train' or 'on_eval' instead when using customized callbacks.
[WARNING] ME(139331:140018904409920,MainProcess):2023-03-13-08:00:18.764.562 [mindspore/dataset/engine/datasets_user_defined.py:766] GeneratorDataset's num_parallel_workers: 8 is too large which may cause a lot of memory occupation (>85%) or out of memory(OOM) during multiprocessing. Therefore, it is recommended to reduce num_parallel_workers to 1 or smaller.
[WARNING] ME(139331:140018904409920,MainProcess):2023-03-13-08:00:19.651.789 [mindspore/dataset/core/validator_helpers.py:804] 'Compose' from mindspore.dataset.transforms.py_transforms is deprecated from version 1.8 and will be removed in a future version. Use 'Compose' from mindspore.dataset.transforms instead.
[Start eval `x3d_kinetics400`]
eval: 1/2484
eval: 2/2484
eval: 3/2484
eval: 4/2484
...
eval: 2482/2484
eval: 2483/2484
eval: 2484/2484
{'Loss':5.988906774751, 'Top_1_Accuracy': 0.7455716586151369, 'Top_5_Accuracy': 0.919987922705314}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,572评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,071评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,409评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,569评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,360评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,895评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,979评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,123评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,643评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,559评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,742评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,250评论 5 356
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,981评论 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,363评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,622评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,354评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,707评论 2 370

推荐阅读更多精彩内容