资讯动态

SlowFast视频行为识别:快慢双通道网络原理与实战详解

发布时间:2026/10/13 18:40:24 来源:尧图企业网站定制
做视频行为识别做到第 8 篇坦白讲越往后越觉得这个领域不是“给视频抽几帧、丢给一个 3D 卷积网络”那么简单。视频里既有一闪而过的挥手也有持续十几秒的对话场景既要看清“发生了什么”还要知道“这件事在更长时间里意味着什么”。所以当我第一次看到 SlowFast Networks for Video Recognition 这个标题时第一反应不是“又一个 3D 卷积变体”而是它终于把视频识别里那种互相矛盾的需求用两个独立分支拆开处理了。这篇内容围绕 CVPR 2019 上提出的快慢双通道网络展开核心就一句话高速通道看动作低速通道看语义再用横向连接把二者合流。对已经懂卷积网络基础、想从静态图像识别跨到视频动作识别的工程师来说这篇会讲清楚 SlowFast 到底改了什么、为什么有效以及自己落地时最容易踩到哪些坑。我会按“背景拆解—结构分析—代码实现—训练调优—经验建议”的顺序来写尽量把结构里每一个设计都能落到可复现的实操层面上。1. 视频行为识别为什么这么难先说点背景1.1 从“静态图像”到“动态视频”的跳跃做图像分类的时候输入是一张固定尺寸的图模型要做的是把“空间特征”压缩成一个类别判断。但到了视频行为识别输入变成了一串连续画面。此时问题不仅是“画面里有什么”而是“画面里的东西在做什么”。同样是“一个人”抬手可以是打招呼也可以是指挥交通同样是“挥拳”配合不同上下文可能是健身动作也可能是打架事件。这里的核心难点在于时间尺度非常宽。眨眼和拿杯子这种动作几百毫秒内就结束了而做一套体操、开一场远程会议往往需要几十秒甚至几分钟才能理解完整语义。早期很多做法是先把视频均匀抽帧再把每一帧丢进 2D 网络里做聚合结果是对快动作不敏感对慢动作又缺乏上下文。后来大家开始堆 3D 卷积网络直接让卷积核在时间维度上滑动。这样能从帧间学到运动信息却带来远超图像模型的参数和计算量。我最早做视频行为识别实验时先是用一个通用 3D 卷积主干接分类头。模型收敛倒是不难难的是怎么同时兼顾“手部细节”和“全局场景”。你如果把网络层数加深全局语义确实会好一点但快速变化的局部动作反而被池化平均掉了如果你把输入帧率提高运动细节是看到了但难训练、显存压力也大。后来我意识到这个矛盾不是靠单个网络结构硬调就能解决的而是需要把“快”和“慢”两种观察方式显式地分开。1.2 SlowFast 这个方向想解决的问题SlowFast 的出发点恰好就在这里与其让同一个卷积网络既负责捕捉高频运动、又负责建模低频语义不如设计两条独立路径。慢路径负责整体场景、物体互动这类偏静态的语义快路径负责快速变化的手势、肢体摆动这类偏动态的运动。两条路径采样率不同通道宽度不同再通过横向连接把快信息融合进慢信息。这个设计解决了一个很现实的问题视频识别网络经常把“空间分辨率”和“时间分辨率”绑在一起。你在空间上保留高分辨率时间上往往就只能仓促处理你增加输入帧率内存和算力立刻告急。SlowFast 的思路是高频运动细节并不需要像静态语义那样多的通道来表达所以快路径可以故意把通道数做少比如只有慢路径的八分之一然后用高帧率去捕捉变化。慢路径虽然帧率低但通道足够宽可以承载更复杂的视觉语义。这样总计算量没有想象中那么可怕却同时拿到了两条时间尺度上的信息。从结果看这个结构在一批公开视频行为数据集上确实把精度往上推了一截。更重要的是它对“动作识别”里最头疼的两个问题——局部快速运动被忽略、全局上下文丢失——给出了一种很清晰的工程解。所以后来做时序动作检测、视频特征提取甚至一些姿态相关的任务我看到很多同行都会拿 SlowFast 作为基础特征提取器效果也都还可以。2. SlowFast 结构拆解快慢两条路2.1 快路径和慢路径的职责划分SlowFast 里最关键的参数是帧率比 alpha 和通道比 beta。一般情况下alpha 取 8beta 取 1/8。慢路径每 8 帧才取一帧作为输入快路径则使用更密集的帧序列因为快路径时间分辨率是慢路径的 8 倍所以如果慢路径输入 T 帧快路径输入就是 8 倍的时间长度。为了控制计算量快路径每层的通道数被压缩到慢路径的 1/8典型的宽度搭配是慢路径 256 通道、快路径 32 通道。用一句话概括慢路径用“又少又宽”的帧管长期语义快路径用“又多又窄”的帧管短期运动。这个分离带来一个非常实际的好处快路径不用在通道数量上跟慢路径做同样的强度它可以专注做一件不擅长的事——快速响应变化。我测试时发现如果把快路径通道加到和慢路径一样宽精度确实可能再涨一点但显存占用会直接起飞。后来我保留原来的 1/8 通道比发现精度的收益远大于通道翻倍的收益。这说明 beta 的设计不只是省资源本身也起到正则作用。在实现上慢路径的输入不是视频原始全速率的帧而是从视频里均匀抽取的低帧率序列。快路径则用相对密集的采样序列。两条路径内部都是标准的 3D 卷积堆叠但快到后期时快路径的时间分辨率会通过池化或者横向连接逐渐向慢路径靠拢。整个网络最终在输出阶段融合再交给分类器。2.2 横向连接到底在传什么如果两条路径各跑各的那就只是两个并行网络在分别提取特征意义不大。SlowFast 的真正创新是横向连接它把快路径的特征按时间下采样后与慢路径特征在通道维度上合并再继续往下走。这个操作看起来简单却让慢路径每一层都能“看到”最近一段时间的运动变化。具体做法是取快路径当前层输出的特征图先用 1x1 卷积把通道数对齐到慢路径的通道规模或目标融合通道然后对时间轴做步长为 alpha 的池化让它的时间长度和慢路径一致。之后把下采样后的快特征和慢特征沿通道维拼接一起送入慢路径下一个卷积块。横向连接的直观类比是把“动态运动信息”作为旁路信息注入到“静态语义主线”。慢路径本来只看稀疏帧容易漏掉两帧之间的动作横向连接把高帧率路径捕捉到的变化补充进来相当于在报告里额外附了一段“慢动作回放摘要”。一开始我怀疑这种做法会不会让网络依赖快路径过多实际观察发现横向连接的输出在训练中会逐渐学习到“什么时候该信任快路径”比如涉及快速肢体动作时融合特征里的激活值明显更强。2.3 为什么这比单条路径更“划算”单条路径的常见做法是直接用高帧率 RGB 输入训练一个很深的 3D 卷积网络。这样做的问题在于每一个卷积层都必须同时建模空间语义和时间差分功能耦合很强。为了捕捉快速运动你必须提高时间分辨率但为了识别长时间语义你又需要较大的感受野和通道容量。两者叠加网络不但臃肿训练也容易顾此失彼。SlowFast 相当于在功能和资源上做了一次分工外包。慢路径不需要高帧率它可以用有限的时间帧去吸收更丰富的空间语义快路径不需要那么多通道因为它不需要理解“目标是什么”只需要回答“有没有在动、往哪动”。于是整体计算量被压在一个基准 3D 卷积网络的 1.5 至 2 倍左右但时间建模能力却明显提升。不过“划算”是有前提的。快路径的高帧率意味着输入张量的时间维度很大如果视频很长或者你直接用原始分辨率训练显存还是会很快被打满。所以实际操作中常常要对输入做限制比如只取一个几秒钟的剪辑片段或者把训练分辨率降到 224x224。关于这块的具体配置我放到后面讲代码落地时展开。3. 代码落地一个可读性优先的 SlowFast 实现3.1 搭建快慢两条支路的骨架先声明一下下面这份代码是我为了讲清楚结构原理做的教学版去掉了论文源码里复杂的 ResNet 变体和初始化细节只保留 SlowFast 最核心的骨架。如果你想直接训练可以在现成框架里找 SlowFast 的预训练实现但如果想理解每一层在做什么建议还是亲手把两个分支搭出来。import torch import torch.nn as nn class ConvBlock3D(nn.Module): def __init__(self, in_channels, out_channels, kernel_size3, stride1, padding1, use_bnTrue): super().__init__() self.conv nn.Conv3d( in_channels, out_channels, kernel_sizekernel_size, stridestride, paddingpadding ) self.bn nn.BatchNorm3d(out_channels) self.relu nn.ReLU(inplaceTrue) self.use_bn use_bn def forward(self, x): x self.conv(x) if self.use_bn: x self.bn(x) return self.relu(x) class SlowPath(nn.Module): def __init__(self, in_channels3, stem_channels64): super().__init__() self.stem ConvBlock3D(in_channels, stem_channels, kernel_size(1, 7, 7), stride(1, 2, 2), padding(0, 3, 3)) def forward(self, x): return self.stem(x) class FastPath(nn.Module): def __init__(self, in_channels3, stem_channels32): super().__init__() self.stem ConvBlock3D(in_channels, stem_channels, kernel_size(5, 7, 7), stride(1, 2, 2), padding(2, 3, 3)) def forward(self, x): return self.stem(x)这里有一个细节慢路径因为输入帧率低在 stem 的第一层卷积时间核很小基本不跨帧做时序运算快路径则可以用稍大的时间核去捕捉相邻帧之间的运动变化。这个设置与两条路径各自的任务是匹配的。3.2 侧连接与瞬时聚合的实现横向连接是 SlowFast 的核心。你需要把快路径特征的时间长度压到与慢路径一致然后拼接到慢路径上。下面这段代码实现了一个标准横向连接class LateralConnection(nn.Module): def __init__(self, fast_channels, slow_channels, alpha8): super().__init__() self.proj nn.Conv3d(fast_channels, slow_channels, kernel_size1) self.time_downsample nn.AvgPool3d( kernel_size(alpha, 1, 1), stride(alpha, 1, 1) ) def forward(self, fast_feat, slow_feat): # fast_feat: [N, C_fast, T_fast, H, W] # slow_feat: [N, C_slow, T_slow, H, W], T_slow T_fast // alpha lateral self.proj(fast_feat) lateral self.time_downsample(lateral) fused torch.cat([slow_feat, lateral], dim1) return fused不同层之间的横向连接输出维度需要和后续慢路径卷积块的输入维度匹配。最简单的做法是在横向连接之后接一个 1x1 卷积把融合后的通道数调整到慢路径下一层期望的输入通道数。如果你用了预训练权重还需要注意各阶段融合位置的通道变化但如果完全从零搭这一层可以自由设计。在 forward 阶段需要先把输入视频拆成快慢两条分支的输入class SlowFastSimpleNet(nn.Module): def __init__(self, num_classes10, alpha8, beta8): super().__init__() slow_stem_channels 64 fast_stem_channels slow_stem_channels // beta # 8 self.slow_path SlowPath(in_channels3, stem_channelsslow_stem_channels) self.fast_path FastPath(in_channels3, stem_channelsfast_stem_channels) self.lateral1 LateralConnection( fast_channelsfast_stem_channels, slow_channelsslow_stem_channels, alphaalpha ) self.classifier nn.Sequential( nn.AdaptiveAvgPool3d((1, 1, 1)), nn.Flatten(), nn.Linear(slow_stem_channels fast_stem_channels, num_classes) ) def forward(self, x): # x: [N, 3, T_fast, H, W] N, C, T, H, W x.shape T_slow T // self.lateral1.time_downsample.kernel_size[0] x_fast x x_slow x[:, :, ::8, :, :] # 均匀取1/8帧简化为直接切片 f_fast self.fast_path(x_fast) f_slow self.slow_path(x_slow) fused self.lateral1(f_fast, f_slow) return self.classifier(fused)这份代码把很多细节刻意简化了。实际模型里横向连接会出现在多个阶段而不是只做一次每条路径里也会有多个残差块堆叠。但核心思想是完整的快慢双路、时间下采样、通道拼接。3.3 数据预处理、采样策略与训练超参准备训练数据时不要把原始视频整段丢进去。我一般先做视频解码按均匀间隔抽帧然后再生成训练 clip。建议把输入视频分成长度约 5 到 8 秒的片段再在片段内随机选择连续的 32 帧作为快路径输入。慢路径通过 alpha8 的间隔采样得到 4 帧输入。也就是说模型实际看到的是“密集的 32 帧加稀疏的 4 帧”。训练我常用这样的配置配置项推荐值说明快路径输入帧数32时间长度约 1 到 2 秒慢路径输入帧数4快路径 8 倍下采样帧率比例 alpha8快慢时间分辨率比通道比例 beta8快路径通道为慢路径的 1/8空间尺寸224x224训练裁剪尺寸batch size32 到 64视显存大小调整学习率0.01SGD配合 cosine 退火优化器SGD momentum 0.9比 Adam 更稳权重衰减1e-4对视频类任务有帮助训练 epoch80 到 120需要足够长的收敛时间数据增强随机裁剪、水平翻转、颜色抖动缓解局部过拟合训练顺序上我建议先把慢路径主干用图像预训练权重初始化再随机初始化快路径这样比全部从零训练收敛快不少。如果条件允许也可以先用低分辨率快速跑一轮小 epoch确认数据加载和 loss 都在正常下降再切回高分辨率训练。这能省掉很多莫名其妙的 bug 排查时间。4. 训练与调优真实场景中踩过的坑4.1 显存爆炸与并行策略SlowFast 刚搭起来的时候显存占用比普通 3D 卷积网络高不少。原因很简单快路径的那 32 帧视频虽然通道数只有慢路径的八分之一但时间维度是慢路径的 8 倍。如果你 batch size 设置太大第一个 back propagation 还没跑完显存就满了。我当时踩过几次之后总结出一套方法。第一使用梯度累计小 batch 多累加几次等效大 batch 的效果第二在快路径局部使用 checkpoint 技术也就是用计算换显存第三把训练分辨率从 256x256 降到 224x224通常能直接减少近两成显存压力。多个 GPU 训练时最好使用同步批归一化否则 batch 太小每个卡上的 BN 统计都不稳定。4.2 训练不稳BN、学习率与数据增强视频行为识别训练非常容易过拟合尤其当数据类别多、每个类别的样本数不均衡时。我遇到过的情况是 loss 在前 20 个 epoch 降得挺好后面开始震荡验证集精度反而往下走。原因通常是学习率没有配合退火策略以及视频增强不够强。我当时把问题定位到两点。一是 BN 层对视频这种高相关帧如果 batch 内视频来自同一个视频的不同片段BN 统计会出现偏差导致训练不稳定。后来我在数据采样时强制同一个 batch 尽量来自不同视频片段问题好了很多。二是数据增强不要只做简单的随机裁剪和翻转可以再加一些时序上的扰动比如随机丢帧、轻微的时间缩放让网络对帧率变化有鲁棒性。这一点对我来说收益很明显。4.3 推理阶段怎么发挥双通道优势训练完的 SlowFast 模型如果推理时只跑一个 clip效果往往有波动。因为动作识别天然依赖上下文一个随机裁出的 2 秒片段可能刚好错过了关键动作。实践里我会把一段视频均匀切出多个 clip每个 clip 独立过模型再把所有预测概率取平均。多 clip 融合通常比单一 clip 高 2 到 4 个点在时序动作检测里尤其明显。另外推理时快路径和慢路径的输入帧率不需要和训练完全一致。如果你的视频本身只有 15 帧每秒训练时的“快路径密集采样”到了推理阶段可能就变成“每帧都是重复采样”。所以我建议推理前先检查一下视频实际帧率必要时重采样到和训练接近的帧率区间否则快路径学到的运动特征会因为帧间间隔过长而失效。5. 我在实践中的心得与建议5.1 什么时候值得用 SlowFast什么时候别用如果你做的是离线视频理解比如视频分类、动作识别、异常事件检测并且算力相对充裕SlowFast 是很稳的方案。尤其数据里既有细粒度的手势变化又有完整的场景上下文时快慢双路的优势非常明显。但如果你的任务强调实时性比如摄像头边缘设备上的动作检测那 SlowFast 可能会让你很痛苦。快路径高帧率带来的计算量在移动端或轻量设备上是很难跑起来的。这种情况下老老实实用轻量 3D 卷积或者先做关键帧抽取再用 2D 网络反而更实在。我见过不少团队看到 SlowFast 指标高就直接搬上嵌入式设备最后为了实时性不得不把输入帧率砍到很低结果快路径彻底失去意义精度还不如原来的轻量模型。5.2 那些论文里没写、但我实测有用的细节最后分享几个比较零散但很实用的经验。第一个如果你打算在一个全新的数据集上微调 SlowFast不要一次性把所有层都放开。先冻结慢路径主干只训练快路径和分类头稳定后再一起微调。这样做能明显降低初始 loss 震荡。第二个快路径的特征在融合后会被“稀释”得很快所以横向连接不宜只放在网络最深层中前期的融合同样重要。我试过只在最后两阶段做融合效果明显不如在每一阶段都加横向连接。第三个如果数据量不太够建议用预训练模型做特征提取而不是从头训练。SlowFast 从头训练需要大量视频数据我这边用几十万样本才勉强把效果练起来小数据集上老老实实用现成预训练权重会省事很多。如果你正在考虑把视频行为识别落地到项目里或者对现有 3D 卷积网络的精度不满意SlowFast 是一个值得先花一周时间试跑的方向。它的核心结构相对清晰网络改动不算大但理念上把“快慢双路”拆开这一点确实值得好好吸收。真跑起来之后你会慢慢发现所谓“更准的视频理解”很多时候不过是在合适的时机把该快的做快把该稳的做稳。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑