TSN 与 TSM:时间片段与通道移位的高效动作识别

FreeGuideOnline 最新 2026-06-19

动作识别入门:TSN 与 TSM 高效时序建模

为什么需要 TSN 与 TSM?

动作识别是视频理解的核心任务,目标是对一段视频中人的行为进行分类(如“游泳”、“弹吉他”、“拥抱”)。早期方法尝试将 2D 卷积网络(如 ResNet)直接应用到每一帧,然后对帧级预测取平均,但这种单帧方式完全忽略了运动信息。3D 卷积(如 C3D、I3D)虽然能够捕获时空特征,但计算量巨大、参数量庞大,难以在普通硬件上训练。

为解决效率与性能的矛盾,时序分割网络(Temporal Segment Networks, TSN)时序移位模块(Temporal Shift Module, TSM) 被提出。它们都基于 2D CNN,却能用极少的额外成本实现强大的时序建模能力,成为视频动作识别领域轻量且高效的基线。


TSN:时间片段采样的稀疏时序融合

核心思想

TSN 的设计哲学是长程时序结构的稀疏采样。它并不处理整个视频,而是将视频均匀分割为 K 个片段(segments),从每个片段中随机抽取一帧(或一小段 snippet)。这些帧分别经过共享的 2D CNN 提取特征,最后通过一种共识函数(如平均池化)融合所有片段的预测。

网络结构

输入视频 -> 分割为3片段 -> [帧1, 帧2, 帧3] -> 共享CNN -> [特征1, 特征2, 特征3] -> 共识(平均)-> 最终预测
  • 分段采样:无论视频多长,只抽取固定数量的帧,保证计算开销恒定。
  • 共享权重:所有片段使用同一个 CNN,模型完全基于 2D 架构。
  • 共识函数:最简单的选择是对所有片段的类别得分取平均,也可使用最大值或加权投票。

为什么有效?

TSN 的假设是:动作可以用少数关键帧来判别。例如“投篮”只需看到起跳和出手的瞬间,而不需要连续几百帧。通过对全视频分段采样,模型被迫关注整体时序结构,而不是局部细节。这种设计极大降低了计算量,同时保留了长时序范围的能力。

TSN 的局限性

虽然 TSN 引入了多帧,但帧与帧之间在时间上是相互独立的——每帧特征提取时看不到其他帧的运动。因此 TSN 对快速运动、动态变化大的动作仍不够鲁棒。为了解决这个问题,研究者开始在 2D CNN 内部加入时序交互,TSM 便是其中的代表。


TSM:零参数零计算的时序融合

核心思想

TSM 的关键操作为通道移位(channel shift)。它的灵感来源于:时序上的信息交换不一定需要 3D 卷积,只需将相邻帧的特征图在通道维度上移动一部分即可。

例如,对于当前帧的一部分通道,我们将其替换为上一帧相同位置的通道值;而另一部分通道则替换为下一帧的通道值。这样,当前帧的特征就自然融合了前后时刻的信息,且无需任何额外参数和 FLOPs

移位操作图解

假设特征图形状为 [T, C, H, W],T 为帧数,C 为通道数。

  1. 将 C 个通道三等分:C/3 向前移位,C/3 保持不变,C/3 向后移位。
  2. 向前移位:把第 t 帧的前 C/3 个通道赋值为第 t-1 帧的对应通道(第 0 帧用自身填充或补零)。
  3. 向后移位:把第 t 帧的后 C/3 个通道赋值为第 t+1 帧的对应通道(最后一帧用自身填充)。

代码示意(简化版):

def shift(x, n_segment=3, fold_div=3):
    # x: [N*T, C, H, W] 经 reshape 为 [N, T, C, H, W]
    nt, c, h, w = x.size()
    n_batch = nt // n_segment
    x = x.view(n_batch, n_segment, c, h, w)
    fold = c // fold_div
    
    out = torch.zeros_like(x)
    # 向前移位: 取前一帧的前 fold 个通道
    out[:, :-1, :fold] = x[:, 1:, :fold]  
    # 中间不变: 中间 fold 个通道保持不变
    out[:, :, fold:2*fold] = x[:, :, fold:2*fold]
    # 向后移位: 取后一帧的后 fold 个通道
    out[:, 1:, 2*fold:] = x[:, :-1, 2*fold:]
    
    # 将边缘帧补全(复制自身)
    out[:, 0, :fold] = x[:, 0, :fold]       # 第一帧向前补自身
    out[:, -1, 2*fold:] = x[:, -1, 2*fold:] # 最后一帧向后补自身
    
    out = out.view(nt, c, h, w)
    return out

这个移位模块可以插入到 ResNet 的每个残差块中,替换掉部分卷积或放在卷积之前。最终网络仍然是 2D CNN,但具备了一定的时序建模能力。

TSM 的两种形式

  • 在线 TSM:只能使用过去的信息(前向移位),适用于实时视频分析。
  • 离线 TSM:可以同时使用过去和未来的信息(双向移位),精度更高,适合离线处理。

为什么 TSM 如此高效?

  • 零参数:移位只改变特征的排列,不引入任何可学习参数。
  • 零 FLOPs:移位本质是内存移动,没有浮点运算增加。
  • 硬件友好:移位操作可以实现为简单的 tensor 索引,在 GPU 上几乎无额外耗时。

实验表明,TSM 在 Something-Something、Kinetics 等数据集上能以 2D CNN 的成本达到接近 3D CNN 的精度。


TSN vs TSM 对比

特性 TSN TSM
时序建模位置 网络末端的共识函数 网络内部的通道移位
帧间交互 无,各自独立提取特征 有,通过通道混合实现帧间通信
额外计算量 几乎为零(只有多头共识) 零,无额外 FLOPs
额外参数
适用场景 长视频粗粒度分类 短时序精细动作、实时推理
典型骨架 BN-Inception, ResNet ResNet-50

何时选择 TSN? 当处理较长视频,且动作可通过少数关键帧区分时,TSN 简单有效,且分段采样的思路天然适合处理长视频。

何时选择 TSM? 当动作需要精细运动信息(如“打开”和“关闭”这种时序相关动作),或需要实时推理且希望保持 2D 网络效率时,TSM 是更优选择。


实践:使用 PyTorch 构建 TSM-ResNet

以下搭建一个最基本的 TSM-ResNet,将原始 ResNet-50 的 Bottleneck 块中加入通道移位。

1. 定义移位函数

import torch
import torch.nn as nn

def temporal_shift(x, n_segment=8, fold_div=3):
    n, c, h, w = x.size()
    n_batch = n // n_segment
    x = x.view(n_batch, n_segment, c, h, w)
    fold = c // fold_div
    
    out = torch.zeros_like(x)
    out[:, :-1, :fold] = x[:, 1:, :fold]       # 向前移位
    out[:, :, fold:2*fold] = x[:, :, fold:2*fold]  # 不变
    out[:, 1:, 2*fold:] = x[:, :-1, 2*fold:]   # 向后移位
    
    # 边界处理
    out[:, 0, :fold] = x[:, 0, :fold]
    out[:, -1, 2*fold:] = x[:, -1, 2*fold:]
    
    return out.view(n, c, h, w)

2. 自定义带移位的 Bottleneck

class BottleneckTSM(nn.Module):
    def __init__(self, original_bottleneck, n_segment=8):
        super().__init__()
        self.conv1 = original_bottleneck.conv1
        self.bn1 = original_bottleneck.bn1
        self.conv2 = original_bottleneck.conv2
        self.bn2 = original_bottleneck.bn2
        self.conv3 = original_bottleneck.conv3
        self.bn3 = original_bottleneck.bn3
        self.relu = original_bottleneck.relu
        self.downsample = original_bottleneck.downsample
        self.n_segment = n_segment

    def forward(self, x):
        identity = x
        # 在第一个卷积之前插入移位
        out = temporal_shift(x, self.n_segment)
        out = self.conv1(out)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)
        return out

3. 替换 ResNet 中的模块

import torchvision.models as models

def make_tsm_resnet50(n_segment=8, num_classes=101):
    model = models.resnet50(pretrained=True)
    # 替换每个 layer 中的 Bottleneck
    for name, module in model.named_children():
        if name in ['layer1', 'layer2', 'layer3', 'layer4']:
            for block in module:
                block.__class__ = BottleneckTSM
                block.n_segment = n_segment
    # 修改全连接层
    model.fc = nn.Linear(model.fc.in_features, num_classes)
    return model

4. 数据加载与训练注意事项

  • 帧采样:对于每个视频,均匀采样 n_segment 帧,然后将这若干帧堆叠成 batch,输入形状为 [batch*n_segment, C, H, W]
  • 标签:同一个视频的 n_segment 帧共享同一个标签,共识函数已经隐含在训练中(通过平均损失)。
  • 学习率:通常从 0.01 开始,使用余弦退火,训练约 50~100 个 epoch。

总结与展望

TSN 和 TSM 为视频动作识别提供了高效、易于部署的 2D 基线。TSN 告诉我们如何用稀疏采样捕获长程结构,TSM 则展示了零成本的帧间通信方法。二者思想也可以融合:使用 TSM 作为特征提取器,TSN 的分段采样与共识仍然可以保留。

进一步发展方向包括:

  • TSM 的变体:如 TEA(Temporal Excitation and Aggregation)引入注意力加权。
  • Video Transformer:TimeSformer、Video Swin Transformer 虽性能更强,但 TSN/TSM 仍然在低资源场景和工业界占有重要地位。

掌握 TSN 和 TSM 的设计哲学,是进入视频理解领域的最佳起点。