X3D
X3D是一篇發表在CVPR2020上的關于視頻動作分類的文章
Code
算法原理
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}
```