- 论文:《Two-Stream Convolutional Networks for Action Recognition in Videos》(用于动作识别的双流卷积神经网络)
- 参考:李沐《双流网络论文逐段精读》
1. 为什么要做视频?
本文是做视频分类的,为啥论文题目是动作识别呢?
2. 双流网络:视频领域开山之作
在这篇文章出现之前,用深度学习来处理视频主要有两种方式:
这两种方式的效果都还不如手工设计的特征。双流网络是第一个能让深度学习网络效果媲美手工设计特征的视频分类结构,从此之后,深度学习在视频领域开始占据主流。
3D网络做视频处理的代表工作是2014年的DeepVideo,把当时能够魔改的CNN结构都在动作识别上试了一遍,实验做的很多。作者为了做出好的效果,还收集了一个很大的数据集:
Sports-1M
。这个数据集有100万视频,时长几十秒到几分钟,视频帧超10亿,直到现在都还在使用。
最终的结果,是在Sports-1M
这么大的数据集上预训练,再在UCF-101
数据集上微调,精度也只有65%,比最好的手工特征方法差了20%,所以结果是非常差的。
作者认为,目前CNN网络无法将视频处理的很好,是因为卷积神经网络很擅长处理静态的外观信息( appearance information
,比如物体形状大小颜色、场景信息等等),而非运动信息(motion information
) 。既然如此,就干脆用另一个网络(光流网络)抽取好运动信息的特征,CNN只需要学习输入的光流和最后的动作信息之间的映射就行(或者说是光流输入到时间流网络的分类之间的映射),这种映射是深度神经网络最擅长的。也就是说CNN本身不需要学习运动信息,也不需要进行时序建模,这些都交给光流做了,所以模型非常的简单。两个网络互不干扰,很好训练也很好优化,最终模型的性能也非常高(见实验部分)。
在相关工作中,作者列出了两种手工设计的特征:
STIP
特征(spatio-temporal interest points):基于局部的时空学习, 演变为今天的3D网络IDT
特征( improved dense trajectories ):dense trajectories是指光流特征。先算出光流,再利用光流找到运动轨迹,然后在物体运动轨迹上抽取特征,这样能很好的利用物体的运动信息。而IDT
就是后续改进的dense trajectories特征。受光流特征的启发,作者将光流也应用到神经网络结构中,也就是本文的双流网络。
3. 贡献
将深度神经网络应用于视频动作识别的难点,是如何同时利用好静止图像上的 appearance information
以及物体之间的运动信息motion information
。本文主要有三点贡献:
双流网络(使用两个卷积神经网络)结构如下:
Spatio Stream Convet
:空间流卷积网络,输入是单个帧画面(静态图片),主要学习场景信息。因为是处理静态图片,所以可以使用预训练的模型来做,更容易优化。最后根据网络输出的特征得出一个logist(假设模型是在UCF-101
数据集上做测试,数据集共101个类,logist是softmax之后的类别概率,那么时间流网络的输出就是一个1×101维的向量)。temporal stream convet
:时间流卷积网络(光流网络),输入是光流图像,通过多帧画面的光流位移来获取画面中物体的运动信息,最后也根据网络输出的特征得出一个logist。
late fusion
融合:两个logist加权平均得到最终分类结果(比如两个softmax向量取平均,再做一个argmax操作)。
late fusion
就是指在网络输出层面做做融合(这里是logits层面),另外还有early fusion
,就是在网络中间层特征上做融合。- 空间流和时间流网络结构几乎是一样的,唯一区别就是输入不一样。前者输入的是静止的视频帧,输入
channel=3
;后者输入是L
张光流图的叠加,输入channnel=2×L
(一般取L=10
)。因为时间流网络输入通道是2L
,所以没法用一般的CNN预训练模型。
optical flow简单说就是每个物体的运动轨迹,运动越明显的区域亮度越高。比如下图左侧由两张图片组成,背景不变,只有人的运动状态变了;右图背景运动状态不变,所以是黑色的,只有图中的人处于运动中,是亮色的。通过提取光流,可以去掉背景噪声、人物穿着性别等和运动无关的特征都忽略掉,所以最后提取的特征能很好的描述运动信息,也变相的相当于视频里的时序信息的特征表示。
光流由一些位移矢量场(displacement vector fields)组成的,其中第t
帧的位移矢量用dt
表示,是通过第t
和第t+1
帧图像得到的。dt
可以拆分成水平部分dtx
和竖直部分dty
。将dt
、dtx
、dty
可视化后结果如下:
UCF-101
数据集视频帧的大小)dt
的可视化显示,射箭动作是超右上走的,维度是240×320×2。
dtx
和垂直位移dty
光流网络主要是为了学习视频中物体的运动信息(时序信息),所以其输入应该是多张光流图的叠加,而非单张光流图(在之前手工设计的特征中,一般是用10帧或者16帧光流图的叠加)。在3.1 章节,讨论了两种叠加方式:直接叠加和根据光流轨迹叠加,示意图如下:(这些光流图都是要输入网络的 ,所以都经过了resize 224×224)
224×224×2L
。最终实验结果表明简单叠加的方式更好,作者也没有解释清楚这一点。
Bi-directional 的操作,就类似BERT一样,都是一种保险、会涨点的操作。光流前向计算和后向计算都是合理的,物体从位置a到位置b或者反过来都是可行的。
具体来说,如果光流有L帧,就把前一半计算前向光流,后一半计算后向光流,最后得到的光流还是L帧,通道数为2L。
1. 光流抽取非常耗时(单张0.06秒)
计算光流的算法来自《 High accuracy optical flow estimation based on a theory for warping》这篇论文,使用的是其GPU实现,计算一帧光流需要约0.06秒,所以抽取光流是非常耗时的。
比如对于
UCF-101
数据集,有约1万视频,每个视频约10秒,每秒30fps(30帧),一共约300万帧。每两帧抽一张光流图,总共耗时约50h
。如果是Sports-1M
这种更大的数据集(100万视频,视频时长长达几分钟),抽取光流就要成千上万个小时了,这样即使是8卡GPU也要抽取一个多月。
2. 光流的密集表示导致其存储困难,无法训练
图片上每个点都有光流值,也就是一个密集的表示。要把这个密集的表示存下来,所耗费的空间会非常大。对于UCF-101
这个小的数据集来说,存下所有的光流也需要1.5T
的空间,如果是Sports-1M
数据集,至少也得是PB级了。即使有这么大的硬盘存储,IO速度也提不起来,还是没法训练。
3.光流预处理为JPEG图
论文在中,作者将光流的密集表示(dense)转为稀疏表示(sparse)以减少光流存储空间,具体来说就是做类似RGB图像的压缩。将光流值全部rescaled到[0,255]的整数,存成JPEG图片。这样每张光流图就十几kb,存储空间大大减小(UCF-101 dataset from 1.5TB to 27GB)。
双流网络从14年提出到现在,这种将光流的密集表示存为图片的预处理方式 一直沿用至今。但其实即使如此,光流的提取速度慢和存储空间大这两点还是受人诟病,后续有很多工作尝试改进光流,或者直接舍弃光流,改为3D网络。
本章双流网络实现细节为:
所有视频帧都 rescale 至最短边为 256
固定抽取25帧:不管视频有多长,都从视频中等间距的抽取25帧视频帧
当时UCF-101
数据集和HMDB-51
数据集视频长度都是5-7秒,按一秒30帧算,一共是150-210帧。固定取25帧就基本是每隔6到8帧取一帧,约0.25秒。
10 crop:取出来的每一帧都做10 crop(224×224)。简单说就是每一帧都裁剪4个边和一个中心,然后翻转图像之后再类似的crop五张图,最终每一帧得到10张图。最终空间流网络的结果就是250 crop(224×224)经过CNN后得到的结果的平均。
光流网络也是先抽取25帧,再取连续的11帧得到10张光流图,输入时间流网络,得到光流网络的结果。两个网络结果做 late fusion
得到最终的模型结果
Two-Stream:任选一帧,然后取剩下的10帧共11帧,计算出10张光流。整个时间跨度是11帧,约0.4s
视频模型做测试的方式是很多样化的。比如很多模型都采用了上面这种25×10 crop的输入方式,也就是常说的250view
来做视频测试。但是也有一些工作,每个视频抽取20、15、10甚至是5帧,所以在对比结果时,也要看一下是不是公平比较。
另外在3D网络中,使用的是30view;引入了vision transformer之后,测试方式又进一步改变了,有12view、4view、3view等等。所以每篇视频论文看结果之前,要看看其测试方式是怎样的。
下图是在UCF-101
数据集试验了空间流网络预训练效果,和时间流网络的光流堆叠方式:
上图是单向/双向光流、加权平均/SVM分类、是否多任务的消融试验(时空流网络融合时,双向光流是不利的)。
通过上面的消融试验确定了网络结构之后,和其它的模型进行性能对比:
DeepVideo
)作者对未来工作的期望有三点:
Sports-1M
数据集);UCF101
精度91.5%);之前的深度学习处理视频的方法没有利用物体的运动信息,导致其效果还不如传统的手工设计的特征效果好,这也是本文的研究动机。引入运动信息有多种方式,光流是其中最好用的一种,其本身就包含了非常准确和强大的物体运动信息在里面。
引入光流的方式是额外引入一个时间流网络,巧妙的利用光流提供的物体运动信息,而不用神经网络自己去隐式地学习运动特征,大大提高了模型的性能。整个双流网络从结构上来说都非常简单,就是沿用AlexNet网络并做了小小的改动,最后的操作也是一个简单、常规的late fusion
融合。
双流网络除了引入光流这一点,也展示了一种可能性:当神经网络即使如何魔改也无法解决某个问题的时候(比如改模型结构或者目标函数),不如给模型提供一些先验信息,模型学不到就帮它学,往往能大幅简化这个任务。所以一个网络无法解决问题,就可以尝试加入另一个网络,使用别的的数据、别的模型。这种多流网络的思想(网络互补),在别的领域也被广为应用,效果也很好。这一点也从侧面验证了数据的重要性,真实场景中收集更多更好的数据,对模型效果的提升更为巨大,也能更好解决模型泛化、偏见等等一系列问题。
双流网络也可以看做是多模态网络,RGB图片和光流可以看做是不同的模态,有点类似CLIP(两个网络输入分别是图片和文本)。
双流网络在视频理解的地位,可看做是AlexNet在图片分类中的地位。一旦证明其做视频的有效性,就马上有大批工作跟进了,称得上是开山之作。
1. 本文有两大贡献:
提出了一个新的模型I3D,即Inflated 3D ConvNet
。
Inflated
是扩大膨胀的意思,这里是指把一个2D模型扩张到一个3D模型(比如直接将ResNet的卷积核从二维替换为三维,池化也使用3D池化等等)。这样做有几个优点:
之前的3D网络参数量太大,也没有合适的视频数据集去预训练,所以之前的3D网络都不能太深(比如C3D模型,其网络只有8层),效果也不如双流网络。
提出了一个新的视频分类数据集Kinetics
,其优点是:
Kinetics
一开始只有400类所以也叫Kinetics 400
,每个类别都超过400个clips(10s左右的视频段落一般叫video clip)。后续又推出了Kinetics 600
、Kinetics 700
;其视频数量分别有30万、50万、60万。UCF101
。
Sports-1M
:2014年提出,有一百万视频,但是基本都是运动类,应用场景受限。Youtube 8M
:2016年由google提出,共有800万视频。但是数据集实在是太大了,一般人即使下载了也根本玩不动,所以比赛时都是直接提供抽取好的特征。UCF101
和HMDB-51
:前者101类,13000个视频;后者51类只有7000个视频。这两个数据集都太小了,无法发挥深度神经网络的威力Kinetics
:类别均衡,难度适中,数据集规模合适,很多人都玩得动,所以被广泛使用。基本现在视频分类的工作是一定要在Kinetics
上跑一个结果。
Kinetics
数据集Quo Vadis
来自1951年的电影《A still from ‘Quo Vadis’ (1951)》,英语译为Where is this going?电影讲的是古罗马一个昏君的故事。作者的意思是,只通过一张图片,是没法确定很多动作的。比如不能确定下面两个演员是将要亲吻还是已经亲吻过了;无论是否亲吻了,下一步动作如何发展也是未知的。只有看完了上下文的视频帧,才知道发生了什么,以及未来会发生什么。Kinetics
。本文提出的I3D模型在Kinetics
上做预训练,然后迁移到别的数据集上,模型性能得到了巨大的提升(UCF101
精度刷到了98%,宣告了这个数据集的终结)。但其实后来大家发现,直接选取
Kinetics
数据集视频的最中间一帧,然后做图像动作分类,模型的准确度已经很高了,根本不需要太多的上下文信息和时序建模能力。
直到现在也很难构建一个很好的视频数据集,让模型能很好的利用上下文信息,进而可以处理长时间、复杂的视频任务,拓展到真实世界的方方面面。
在本文1.1中已经引用了这张图,介绍了视频理解模型的几种结构,再次做个说明:
CNN+LSTM
:即CNN抽取关键帧特征,LSTM进行时序建模3D Conv
:将视频帧(分割好的一个个的视频段,每段含有K张图片)直接输入3D 的CNN网络进行时空学习。Two stream(late fusion
)`:如果不想用LSTM进行时序建模,也不想用3D网络直接进行时空学习,那么还可以使用光流来得到时序信息(运动信息)。3D Fused Two stream(early fusion
)`:双流网络的改进。作者认为双流网络中,两个网络的输出只是进行简单的加权平均来处理,效果还不够。所以将其替换为一个较小的3D网络,进一步融合特征。实验证明这种先进行2D卷积网络训练,再进行3D卷积网络融合的效果更好。Two stream 3D ConvNet
:I3D是3D网络和双流网络的结合。因为单纯的使用3D网络,模型效果还是不好,加上光流之后,可以大大提高模型性能。所以可以说I3D=Two stream+3D Conv
。另外两个分支网络都已经是3D网络了,也就不需要另外加一个3D网络进行融合,直接加权平均得到最终的结果就行。 扩张方式简单粗暴,其它网络结构都不变,就是把2D的卷积核加一维变为3D(K*K —> K*K*K
),2D池化改为3D池化。一直到最新的video swin transformer(2022年),将swin transformer从2D扩张到3D,也是使用这种方式。
验证模型是否正确初始化:使用预训练模型初始化自己的模型时,如果同一张图片,分别输入原模型和初始化后的模型,最终输出的结果都一样,就说明原模型的参数初始化是对的(因为两个模型的结构和输入都是一样的)。
受此启发,作者将一张图复制粘贴N次就得到了一个视频,这个视频每个时间步上都是同一张图片(a boring video)。将图片
x
x
x和其复制N次得到的视频
x
′
{x}'
x′分别输入2D网络
f
f
f和3D网络
f
′
{f}'
f′,将后者的网络除以N,如果和2D网络的输出一样,则说明3D网络初始化OK。
比如2D网络输出设为 y = f θ ( x ) y=f_{\theta }(x) y=fθ(x),3D网络输出为 y ′ = f θ × N ′ ( x × N ) = N × f θ ( x ) {y}'={f}'_{\theta \times N}(x\times N)=N\times f_{\theta }(x) y′=fθ×N′(x×N)=N×fθ(x)。然后将3D网络的所有 2D filters权重除以N,那么就等于 N × f θ ( x ) ÷ N = f θ ( x ) N\times f_{\theta }(x)\div N= f_{\theta }(x) N×fθ(x)÷N=fθ(x),和2D网络输出结果一致。
Bootstrapping初始化具体实现代码如下(来自dmlc/gluon-cv):
def init_weights(self, ctx):
"""Initial I3D network with its 2D pretrained weights."""
self.first_stage.initialize(ctx=ctx)
self.res_layers.initialize(ctx=ctx)
self.head.initialize(ctx=ctx)
if self.pretrained_base and not self.pretrained:
if self.depth == 50:
resnet2d = resnet50_v1b(pretrained=True)
elif self.depth == 101:
resnet2d = resnet101_v1b(pretrained=True)
else:
print('No such 2D pre-trained network of depth %d.' % (self.depth))
weights2d = resnet2d.collect_params() # 获取2D网络的预训练参数
if self.nonlocal_cfg is None:
weights3d = self.collect_params() # 获取3D网络的参数
else:
train_params_list = []
raw_params = self.collect_params()
for raw_name in raw_params.keys():
if 'nonlocal' in raw_name:
continue
train_params_list.append(raw_name)
init_patterns = '|'.join(train_params_list)
weights3d = self.collect_params(init_patterns) # 获取3D网络的参数
# 下面一行是判断句式,2D网络和3D网络的结构应该一样,参数长度一样
assert len(weights2d.keys()) == len(weights3d.keys()), 'Number of parameters should be same.'
dict2d = {}
for key_id, key_name in enumerate(weights2d.keys()):
dict2d[key_id] = key_name
dict3d = {}
for key_id, key_name in enumerate(weights3d.keys()):
dict3d[key_id] = key_name
dict_transform = {}
for key_id, key_name in dict3d.items():
dict_transform[dict2d[key_id]] = key_name
cnt = 0
for key2d, key3d in dict_transform.items():
# 通过for循环,一步步地将2D网络参数转为3D网络的参数
if 'conv' in key3d:
temporal_dim = weights3d[key3d].shape[2]
temporal_2d = nd.expand_dims(weights2d[key2d].data(), axis=2)
inflated_2d = nd.broadcast_to(temporal_2d, shape=[0, 0, temporal_dim, 0, 0]) / temporal_dim
# 上一步就是Bootstrapping操作,将2D网络参数复制temporal_dim份,然后再除以temporal_dim
assert inflated_2d.shape == weights3d[key3d].shape, 'the shape of %s and %s does not match. ' % (key2d, key3d)
weights3d[key3d].set_data(inflated_2d)
cnt += 1
print('%s is done with shape: ' % (key3d), weights3d[key3d].shape)
if 'batchnorm' in key3d:
assert weights2d[key2d].shape == weights3d[key3d].shape, 'the shape of %s and %s does not match. ' % (key2d, key3d)
weights3d[key3d].set_data(weights2d[key2d].data())
cnt += 1
print('%s is done with shape: ' % (key3d), weights3d[key3d].shape)
if 'dense' in key3d:
cnt += 1
print('%s is skipped with shape: ' % (key3d), weights3d[key3d].shape)
assert cnt == len(weights2d.keys()), 'Not all parameters have been ported, check the initialization.'
具体来说,I3D
使用的是Inception-V1
进行3D扩张,模型结构如下图所示,整体改动还是很小的。
Inflated Inception-V1
结构,就是卷积核都加了一个维度(K*K —> K*K*K
,上图蓝色框)。池化层也加了一个维度,但时间维度不进行下采样。比如池化是3*3—>1*3*3
而非3*3*3
,对应的stride是2*2—>1*2*2*
,而非2*2*2*
。不过在后面几个阶段进行了时间维度的下采样(后三个池化层)Inception Module
,和之前2D结构一样,只是卷积核变为了3D。之所以使用Inception-V1非ResNet,是因为当年(2016年——2017年),很多视频工作的消融表明,使用I
nception-V1
的效果比ResNe
t更好。但是ResNet统治力是在是太强了,一年后《Non-local Neural Networks》用ResNet
实现了I3D
模型,并且加入了self-attention
。后续大家一般提到I3D
,都是指其ResNet
实现的版本。最近几年的工作也表明,2D卷积网络扩张到3D时。池化层的时间维度最好不要进行下采样。比如输入是64帧,输出也是64帧,这个对应的实际只有2秒左右,已经很短了。
下图是将之前讲的五种模型都在Kinetics 400
数据集上进行预训练,再进行测试:
Kinetics 400
数据集每个视频都是10s左右。为了保证公平,使用整个视频来做测试,即测试的视频时长都是10s左右。I3D
效果最好。Kinetics 400
数据集进行预训练,而不用ImageNet,效果也很好I3D
效果最好I3D
和单独的时间流网络I3D
、完整的I3D
;以及这三种模型只在Kinetics 400
数据集上进行预训练的效果。后面三个模型的效果也不差,说明只用视频数据集进行预训练效果就已经很好了。Kinetics 400
数据集的迁移效果非常好,证明视频领域,使用预训练模型进行微调的方式效果也是非常好的。Kinetics 400
上预训练模型,测试时其它层冻住,只训练最后的全连接层Kinetics 400
上预训练模型,测试时整个网络进行微调以上实验都只是做了视频分类任务。
本文从两个方面解决了视频模型的训练问题:
Inflating+Bootstrapping
);Kinetics 400
数据集进行预训练,是一个不错的选择(不需要依赖于ImageNet预训练的模型参数)。 I3D
最大的亮点就是Inflating
操作,不仅不用再从头设计一个3D网络,直接使用成熟的2D网络进行扩充就行,而且看还可以使用2D网络的预训练参数,简化了训练过程,使用更少的训练时间达到了更好的训练效果。
I3D
的最终结果,也超过了之前的2D CNN或者双流网络(UCF101
精度刷到98%
)。所以自从I3D
在2017年提出之后,到2020年,3D CNN
基本霸占了整个视频理解领域,双流网络瞬间就不香了,直到vision transformer
的出现。