X3D
X3D是一篇发表在CVPR2020上的关于视频动作分类的文章
原文链接
Code
Gitee
Github
NoteBook
算法原理
X3D的工作受机器学习中特征选择方法的启发,采用一个简单的逐步拓展网络的方法,以X2D图像分类模型为基础,分别在宽度、深度、帧率、帧数以及分辨率等维度逐步进行拓展,从2D空间拓展为3D时空域。每一次只在一个维度上进行拓展,并在计算量和精度上进行权衡,选取最佳的拓展方式。
作者对比了图像分类网络的发展史,这些图像分类模型经历了对深度、分辨率、通道宽度等维度的探索,但视频分类模型只是简单的对时间维度进行扩张。因此作者提出了对不同维度改进的思考。
3D网络最佳的时间采样策略是什么?长的时间序列和较为稀疏的采样是否优于短时间内的稠密采样?(采样帧率)
是否需要一个更好的空间分辨率?目前的工作都为提高效率而使用低分辨率。是否存在一个最大空间分辨率导致性能饱和?(空间分辨率)
更快的帧率+更“瘦”的模型好亦或更慢的帧率+更“宽”的模型好?也即slow分支和fast分支哪种的结构更好?又或者存在一个二者的中间结构更好?(帧率与宽度)
当增加网络宽度时,是增加全局的宽度好还是增加bottleneck的宽度好?(宽度,inverted bottlenetck结构的借鉴)
网络变深的同时,是否应该增加输入的时空分辨率以保证感受野大小足够大?又或者应该增大不同的维度?(深度与时空分辨率)
X3D整体网络结构如上,卷积核的维度表示为{T×$S^2$ , C }。X3D通过6个轴来对X2D进行拓展,X2D在这6个轴上都为1。
拓张维度:
1. X-Fast:采样帧间隔
2. X-Temporal:采样帧数
3. X-Spatial:空间分辨率
4. X-Depth:网络深度
5. X-Width:网络宽度
6. X-Bottelneck:bottleneck宽度
Forward expansion
前向拓张是给定复杂度,逐步逐维度进行拓张。
首先给定两个指标,一个是衡量当前扩张因子X好坏的J(X),该指标得分越高,拓展因子越好,得分越低,拓展因子越差,这对应的是模型的准确率。第二个是复杂度评判因子C(X),对应的是网络所需的浮点操作计算量,那么目标即为在给定复杂度C(X)=c的条件下,使得J(X)最大的扩张因子。
在网络尝试寻找最佳的拓展因子时,每一步只扩张一个维度,其他维度保持不变,而每一步最好的扩张因子被保留,接着进行下一步扩张。即在初始阶段,模型为X2D,对应着一个计算复杂度,然后给定一个目标复杂度,模型要通过每次改变一个因子,然后一步步变换到目标复杂度。且每一次改变所对应的改变量也是定义好的,即让当前的模型的复杂度变成两倍。再者,每一步的扩张是渐进式的,也即复杂度约2倍增长。这种方法可以看成是坐标下降法的特殊形式,扩张2倍的各维度操作具体如下:
1. X-Fast:
2. X-Temporal:
3. X-Spatial:
4. X-Depth:
5. X-Width:
6. X-Bottelneck:
Backward contraction
后向收缩是在超过复杂度时,进行回溯收缩。
由于前向扩展只在离散步骤中产生模型,如果目标复杂度被前向扩展步骤超过,他们执行后向收缩步骤以满足所需的目标复杂度。此收缩被实现为上一次展开的简单缩减,以便与目标相匹配。例如,如果最后一步将帧率提高了两倍,那么他们就会向后收缩将帧率降低到一个小于2的倍数,以大致匹配所需的目标复杂度。
渐进式拓张
扩张任意一个维度都增加了准确率,验证了最初的想法。
第一步扩张的不是时间维度,而是bottleneck宽度,这验证了MobileNetV2中的倒置残差结构,作者认为原因可能是这些层使用了channel-wise卷积十分轻量,因此首先扩张这个维度比较economical。且不同维度准确率变化很大,扩张bottleneck宽度达到了55.0%,而扩张深度只有51.3%。
第二步扩张的为帧数(因为最初只有单帧,因此扩展采样帧间隔和帧数是等同的),这也是我们认为“最应该在第一步扩张的维度”,因为者提供更多的时间信息。
第三步扩张的为空间分辨率,紧接着第四步为深度,接着是时间分辨率(帧率)和输入长度(帧间隔和帧数),然后是两次空间分辨率扩张,第十步再次扩张深度,这符合直观的想法,扩张深度会扩张滤波器感受野的大小。
值得注意的是,尽管模型一开始宽度比较小,但直到第十一步,模型才开始扩张全局的宽度,这使得X3D很像SlowFast的fast分支设计(时空分辨率很大但宽度很小),最后图里没显示扩张的两步为帧间隔和深度。
结果
?环境准备
????????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 .
训练流程
```python
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数据集。
```python
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进行归一化处理。
```python
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维的特征向量,再由线性层将其变换到类别数量。
```python
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)
```
设置学习率、优化器和损失函数
```python
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)
```
初始化模型
```python
# 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'))
```
部分结果
```text
[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为例
```python
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)
```
构建测试用数据集,并作相应的数据增强
```python
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()
```
构建网络
```python
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)
```
定义损失函数并加载预训练网络
```python
# 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)
```
设置评估参数并初始化网络
```python
# 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)
```
开始测试
```python
# Begin to eval.
print('[Start eval `{}`]'.format('x3d_kinetics400'))
result = model.eval(dataset_eval,
? ? ? ? ? ? ? ? ? ? callbacks=[print_cb],
? ? ? ? ? ? ? ? ? ? dataset_sink_mode=False)
print(result)
```
测试结果
```text
[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}
```