【第二十八周】:Temporal Segment Networks:用于视频动作识别的时间分段网络

news2025/3/25 12:11:03

TSN

  • 摘要
  • Abstract
  • 文章信息
  • 引言
  • 方法
    • 时间分段采样
    • 分段聚合
    • 输入模态
    • 聚合函数
    • 多尺度时序窗口集成(M-TWI)
    • 训练
  • 代码实现
  • 实验结果
  • 总结

摘要

本篇博客介绍了时间分段网络(Temporal Segment Network, TSN),这是一种针对视频动作识别的高效深度学习框架,其核心思想是通过分段稀疏采样和全局时序建模解决传统方法在长时程动作建模中的计算冗余与局部片段覆盖不足问题。TSN将视频均匀划分为多个时间段,每个段随机抽取一个短片段,利用共享权重的卷积网络提取片段特征,并通过聚合函数(如平均池化、Top-K池化)融合全局时序信息生成视频级预测。针对未修剪视频的背景干扰问题,TSN提出多尺度时序窗口集成(M-TWI),结合滑动窗口与自适应池化抑制噪声。关键技术包括:通过稀疏采样覆盖长视频内容,实现计算成本与视频时长无关;多模态输入(RGB、光流、RGB差异)互补时空特征;跨模态初始化与部分批量归一化缓解小数据集训练过拟合。实验表明,TSN在HMDB51(71.0%)、UCF101(94.9%)等数据集上达到当时最优性能。其优势在于高效性与泛化能力,但依赖光流计算且预训练迁移成本较高。未来可探索轻量化模态融合、端到端多模态联合优化,以及动态背景下的长时动作建模增强。

Abstract

This blog introduces the Temporal Segment Network (TSN), an efficient deep learning framework for video action recognition. Its core concept addresses computational redundancy and insufficient local segment coverage in traditional methods for long-term action modeling through segment-based sparse sampling and global temporal modeling. TSN divides videos into multiple temporal segments, randomly samples a short snippet from each segment, extracts features using convolution networks with shared weights, and generates video-level predictions by aggregating global temporal information through fusion functions (e.g., average pooling, Top-K pooling). To mitigate background noise in untrimmed videos, TSN proposes Multi-scale Temporal Window Integration (M-TWI), combining sliding windows with adaptive pooling to suppress interference. Key techniques include: 1) Sparse sampling enabling efficient coverage of long videos with computation cost independent of video duration; 2) Multi-modal inputs (RGB, optical flow, RGB difference) complementing spatiotemporal features; 3) Cross-modality initialization and partial batch normalization alleviating overfitting on small datasets. Experiments show TSN achieves state-of-the-art performance on datasets including HMDB51 (71.0%) and UCF101 (94.9%). Its strengths lie in efficiency and generalization capability, though limitations include reliance on optical flow computation and high pretraining transfer costs. Future directions may explore lightweight modality fusion, end-to-end multi-modal joint optimization, and enhanced long-term action modeling in dynamic backgrounds.


文章信息

Title:Temporal Segment Networks for Action Recognition in Videos
Author:Limin Wang, Yuanjun Xiong, Zhe Wang, Yu Qiao, Dahua Lin, Xiaoou Tang, and Luc Van Gool.
Source:https://arxiv.org/abs/1705.02953


引言

  视频动作识别在安防、行为分析等领域有重要应用,其核心挑战在于同时捕捉视频的外观特征和动态时序信息,这面临尺度变化、视角改变等难题。。
  卷积神经网络(ConvNets)在图像分类中表现优异,但在视频动作识别中优势有限。原因如下:
首先,尽管长时结构已被证明在理解视频中的动态性有至关重要的作用,但当前大多数方法仅关注短时运动(如10帧),缺乏对长时程时间结构的建模。而且一些最近的改进主要依赖于预定义采样间隔的密集采样,这对计算资源的要求极高,且可能会导致视频的重要信息丢失。
其次,现有的动作识别方法大多是针对修剪视频设计,难以直接应用于未修剪视频(如网络视频),其中背景干扰严重。
第三,动作识别的公开数据集的规模小,导致模型易过拟合。而且光流提取等预处理步骤计算成本高,限制了实时性需求。
以上问题促使作者从以下三个方面研究动作识别问题:

  1. 如何有效地学习捕捉长时间结构的视频表示;
  2. 如何利用这些学习的 ConvNet 模型来更真实地设置未修剪的视频;
  3. 如何在给定有限训练样本的情况下有效地学习 ConvNet 模型并将其应用于大规模数据。

方法

论文提出一种基于稀疏的时间采样策略和视频级监督框架,TSN。
在这里插入图片描述
TSN流程如下:

  1. 将输入视频均匀划分为 K 个时间段(Segments)(如 K=3 或 7),每个时间段覆盖视频的不同部分。
  2. 从每个时间段中 随机抽取一个短片段(Snippet)(如单帧、光流序列或 RGB 差异)。
  3. 对每个片段通过 共享权重的卷积网络(ConvNet) 提取特征,生成片段级别的类别预测分数。
  4. 使用 聚合函数 融合所有片段的预测结果,生成视频级分类分数。
  5. 对聚合后的分数进行 Softmax 归一化,输出视频的最终动作类别概率。

时间分段采样

视频中的连续帧具有高度的冗余性,即连续的帧在很大程度上是一样的。所以需要使用稀疏采样,鉴于视频的长时间结构的有效性,需要在整个视频上进行采样。
本文提出的采样方法如下:
先将整个视频V分为持续时间相等的K个段 { S 1 , S 2 . . . . . . S K } \{S_1,S_2......S_K\} {S1,S2......SK}(论文中的segment),然后在每个段中随机采样一个片段(论文中的snippet),得到片段序列 ( T 1 , T 2 . . . . . . T K ) (T_1,T_2......T_K) (T1,T2......TK),这就是后面建模需要用到的输入。
通过这种采样方式,可以避免连续帧的高度冗余,还能对整个视频进行采样。另外,这种采样方法使得此框架的计算成本不依赖于视频的持续时间,无论视频多长,都是分为K短,采样K个片段,并对K个片段进行后续计算。

分段聚合

分段聚合将片段级预测聚合到视频级分数中。
经过分段采样得到的片段序列 ( T 1 , T 2 . . . . . . T K ) (T_1,T_2......T_K) (T1,T2......TK)经过共享参数的卷积后得到各自对应的动作类别得分(每个片段分别预测),得到片段级预测,表示为: F = ( F ( T 1 ; W ) , F ( T 2 ; W ) , ⋯   , F ( T K ; W ) ) . \begin{array}{l}\\ F=\left(\mathcal{F}(T_1;\mathbf{W}\right),\mathcal{F}(T_2;\mathbf{W}),\cdots,\mathcal{F}(T_{K};\mathbf{W})).\end{array} F=(F(T1;W),F(T2;W),,F(TK;W)).其中 W \mathbf{W} W表示卷积操作的参数。
将得到的片段级预测结果经过分段聚合函数 G \mathcal{G} G得到视频级预测,表示为: G = G ( F ( T 1 ; W ) , F ( T 2 ; W ) , ⋯   , F ( T K ; W ) ) . \begin{array}{l}\\ G=\mathcal{G}(\mathcal{F}(T_1;\mathbf{W}),\mathcal{F}(T_2;\mathbf{W}),\cdots,\mathcal{F}(T_{K};\mathbf{W})).\end{array} G=G(F(T1;W),F(T2;W),,F(TK;W)).常用的聚合方法有:平均池化、最大池化、加权平均。
损失的计算是在分段聚合的基础上进行的,采用的是交叉上损失函数: L ( y , G ) = − ∑ i = 1 C y i ( g i − log ⁡ ∑ j = 1 C exp ⁡ g j ) \mathcal{L}(y,\mathbf{G})=-\sum_{i=1}^Cy_i\left(g_i-\log\sum_{j=1}^C\exp g_j\right) L(y,G)=i=1Cyi(gilogj=1Cexpgj)其中, C C C是动作类别的数量, g j g_j gj G G G的第 j j j个维度, y i y_i yi是类别 i i i的真实标签。
则损失相对于模型参数 W \mathbf{W} W的梯度为: ∂ L ( y , G ) ∂ W = ∂ L ∂ G ⋅ ∂ G ∂ W \frac{\partial\mathcal{L}(y,\mathbf{G})}{\partial\mathbf{W}}=\frac{\partial\mathcal{L}}{\partial\mathbf{G}}\cdot\frac{\partial\mathbf{G}}{\partial\mathbf{W}} WL(y,G)=GLWG其中 G G G K K K F ( T K ) F(T_K) F(TK)构成,由链式法则: ∂ G ∂ W = ∑ k = 1 K ∂ G ∂ F ( T k ) ⋅ ∂ F ( T k ) ∂ W \frac{\partial\mathbf{G}}{\partial\mathbf{W}}=\sum_{k=1}^K\frac{\partial\mathbf{G}}{\partial F(T_k)}\cdot\frac{\partial F(T_k)}{\partial\mathbf{W}} WG=k=1KF(Tk)GWF(Tk)所以最终的损失函数为: ∂ L ( y , G ) ∂ W = ∂ L ∂ G ∑ k = 1 K ∂ G ∂ F ( T k ) ∂ F ( T k ) ∂ W \frac{\partial\mathcal{L}(y,\mathbf{G})}{\partial\mathbf{W}}=\frac{\partial\mathcal{L}}{\partial\mathbf{G}}\sum_{k=1}^K\frac{\partial\mathbf{G}}{\partial F(T_k)}\frac{\partial F(T_k)}{\partial\mathbf{W}} WL(y,G)=GLk=1KF(Tk)GWF(Tk)从以上公式中也可看出时间分段网络可以从整个视频中学习模型参数,而不是从一小段视频中学习模型参数。

视频级分数需要经过预测函数 H \mathcal{H} H来对最终的类别分数进行预测: T S N ( T 1 , T 2 , ⋯   , T K ) = H ( G ( F ( T 1 ; W ) , F ( T 2 ; W ) , ⋯   , F ( T K ; W ) ) ) . \begin{array} {l}\mathrm{TSN}(T_1,T_2,\cdots,T_K)= \mathcal{H}(\mathcal{G}(\mathcal{F}(T_1;\mathbf{W}),\mathcal{F}(T_2;\mathbf{W}),\cdots,\mathcal{F}(T_K;\mathbf{W}))). \end{array} TSN(T1,T2,,TK)=H(G(F(T1;W),F(T2;W),,F(TK;W))).
H \mathcal{H} H一般使用softmax函数。

输入模态

与静态图像不同,视频的额外时间维度为动作理解提供了另一个重要线索,即运动。使用密集的光流场作为运动表示的来源被证明是有效的。论文从两个方面扩展了这种方法,即准确性和速度。如下图所示,除了 RGB 和光流的原始输入模态外,作者还研究了其他两种模态:RGB差异(RGB difference)和扭曲的光流场(warped optical flow fields),这两种模态与光流场一样,作为时间流的输入。
在这里插入图片描述
图中的四列分别表示:RGB 图像、RGB 差异、光流场(x,y 方向)和变形光流场(x,y 方向)
RGB difference(RGB差异)
RGB Differences是通过计算连续帧之间RGB像素值的差异来捕捉表观运动信息的模态。它直接利用原始RGB帧的差异作为运动特征,无需复杂的光流计算。

实现方式:选择相邻的两帧图像 I t I_t It I t + 1 I_{t+1} It+1,逐像素计算两帧的绝对差值: D t = ∣ I t + 1 − I t ∣ D_t=|I_{t+1}-I_t| Dt=It+1It为了捕捉更长时间跨度的变化,可以计算多对连续帧的差分,并将它们堆叠在一起。
RGB difference通过简单差异反映物体移动或形变,避免了光流提取的高计算成本,适合快速动作建模。

Optical Flow Fields(光流场)
光流场描述视频中每个像素点在连续帧中的运动向量(水平和垂直方向位移),直接编码物体运动的时空信息。

实现方式:使用TV-L1光流算法,计算每对连续帧的光流场。光流场分为水平(x方向)和垂直(y方向)两个通道。为了捕捉更长时间跨度的变化,可将多个光流帧堆叠成输入序列。
Optical Flow Fields精准对运动建模:直接反映像素级运动,适合复杂动作。

Warped Optical Flow(变形光流场)
对原始光流进行校正,消除相机运动的影响,专注于主体(如人体)自身运动。
实现方式:变形光流场通过 全局运动估计 和 反向补偿 得到,公式为:
WarpedFlow ( x , y ) = H − 1 ⋅ OriginalFlow ( x , y ) \text{WarpedFlow}(x,y)=H^{-1}\cdot\text{OriginalFlow}(x,y) WarpedFlow(x,y)=H1OriginalFlow(x,y)其中, H H H是全局运动(如相机运动)的仿射/单应变换矩阵; H − 1 H^{-1} H1 H H H 的逆矩阵,用于反向补偿。
Warped Optical Flow能够抑制由相机移动引起的背景干扰(如跟拍镜头中的背景偏移),增强对主体运动的敏感性。

三种时间流模态对比

模态优势劣势适用场景
RGB Differences实时性强运动表征较粗糙实时系统、轻量级部署
Optical Flow运动建模精准计算成本高(依赖TV-L1光流提取)高精度离线分析
Warped Flow抗相机运动干扰校正步骤增加复杂度复杂背景(如运动赛事、街景)

聚合函数

论文中分析了五种聚合函数:最大池化、平均池化、Top K 池化、加权平均和注意力加权。

  1. 最大池化:为每个动作类别选取所有片段中得分最高的片段作为视频级预测,仅保留最显著片段的激活值。其优点是突出最具判别性的单个片段,适合快速定位关键动作片段。缺点是忽略其他片段的信息,无法建模长时程多阶段动作。
  2. 平均池化:将所有片段的类别得分取平均作为视频级预测,均匀融合所有片段信息。其优点是简单高效,充分利用全局信息,适合修剪视频。但易受背景片段干扰,未修剪视频中性能下降。
  3. Top K 池化:选择每个类别得分最高的 K K K 个片段进行平均,平衡判别性与冗余抑制。其优点是能保留重要片段,抑制噪声,适合未修剪视频。
  4. 加权平均:为每个片段分配固定权重,加权平均得分,权重为可学习参数。其优点是能通过权重区分不同阶段的重要性。但权重与视频内容无关,灵活性不足。
  5. 注意力加权:动态计算每个片段的注意力权重,加权融合得分,其中权重基于片段特征生成。其优点是能自适应聚焦关键片段,抑制背景,适合复杂场景。但其计算复杂度较高,需联合优化注意力模型。

多尺度时序窗口集成(M-TWI)

在未修剪的视频中(如监控录像、网络视频),动作可能只占视频的一小部分,大部分内容是无关的背景。传统方法直接对整个视频平均处理时,背景噪声会严重影响动作识别的准确性。
多尺度时序窗口集成(M-TWI)的步骤如下:

  1. 多尺度窗口划分:
    将视频按不同时间长度划分为窗口(如1秒、2秒、4秒、8秒、16秒)。
    目的:覆盖不同持续时间的动作(短暂动作用短窗口,长动作用长窗口)。
  2. 窗口内最大池化
    对每个窗口内的所有片段进行动作识别,仅保留得分最高的片段。
    作用:突出窗口中最可能是动作的部分,忽略低分背景片段。
  3. Top-K池化抑制背景
    对每个尺度的所有窗口,选择得分最高的前K个窗口(例如按窗口数量动态调整K值)。
    效果:筛除低分窗口(可能包含背景),仅整合高分动作窗口。
  4. 多尺度结果融合
    将不同尺度(1秒、2秒、4秒等)的Top-K窗口结果平均,得到最终预测。
    优势:综合不同时间粒度的信息,提升动作定位鲁棒性。

训练

用于动作识别的现有人工注释数据集在大小方面受到限制。在实践中,在这些数据集上训练深度 卷积神经网络容易出现过拟合。为了解决这一问题,文中采取了三种方法:

  1. 跨模态初始化(Cross Modality Initialization):利用大规模图像数据(ImageNet)预训练模型初始化网络。在空间流(RGB)上直接使用ImageNet预训练的权重。在时间流(光流、RGB差异等)上,将RGB模型的第一个卷积层权重在RGB通道上取平均,复制到时间流输入的多个通道(如光流的x/y方向),其他层直接继承RGB模型的参数。
  2. 部分批量归一化(Partial Batch Normalization):迁移学习中,批量归一化(BN)的均值和方差可能因目标数据集小而产生偏差。此方法仅更新第一个BN层以适应输入模态的分布差异,同时固定其他BN层以防止过拟合,保持预训练模型的稳定性。
  3. 数据增强(Data Augmentation):包括常规方法随机裁剪和水平翻转,还新增了角点裁剪和多尺度抖动。角点裁剪从图像四角和中心裁剪,避免模型过度关注中心区域。多尺度抖动是随机选择裁剪尺寸或调整至固定尺寸。

代码实现

下面是官方实现的TSN的网络搭建

from torch import nn

from ops.basic_ops import ConsensusModule, Identity
from transforms import *
from torch.nn.init import normal, constant

class TSN(nn.Module):
    def __init__(self, num_class, num_segments, modality,
                 base_model='resnet101', new_length=None,
                 consensus_type='avg', before_softmax=True,
                 dropout=0.8,
                 crop_num=1, partial_bn=True):
        """
        初始化TSN模型。

        参数:
        num_class (int): 分类的类别数量。
        num_segments (int): 视频的分段数量。
        modality (str): 输入模态,可选值为 'RGB', 'Flow', 'RGBDiff'。
        base_model (str): 基础模型,默认为 'resnet101'。
        new_length (int): 每个分段的长度,默认为 None。
        consensus_type (str): 共识模块的类型,默认为 'avg'。
        before_softmax (bool): 是否在共识模块前进行Softmax操作,默认为 True。
        dropout (float): Dropout的概率,默认为 0.8。
        crop_num (int): 裁剪的数量,默认为 1。
        partial_bn (bool): 是否使用部分批量归一化,默认为 True。
        """
        super(TSN, self).__init__()
        self.modality = modality
        self.num_segments = num_segments
        self.reshape = True
        self.before_softmax = before_softmax
        self.dropout = dropout
        self.crop_num = crop_num
        self.consensus_type = consensus_type
        # 检查在不使用Softmax前的情况下,共识模块类型是否为 'avg'
        if not before_softmax and consensus_type != 'avg':
            raise ValueError("Only avg consensus can be used after Softmax")

        # 根据模态设置每个分段的长度
        if new_length is None:
            self.new_length = 1 if modality == "RGB" else 5
        else:
            self.new_length = new_length

        # 打印模型初始化信息
        print(("""
Initializing TSN with base model: {}.
TSN Configurations:
    input_modality:     {}
    num_segments:       {}
    new_length:         {}
    consensus_module:   {}
    dropout_ratio:      {}
        """.format(base_model, self.modality, self.num_segments, self.new_length, consensus_type, self.dropout)))

        # 准备基础模型
        self._prepare_base_model(base_model)

        # 准备TSN模型的全连接层
        feature_dim = self._prepare_tsn(num_class)

        # 根据模态对基础模型进行修改
        if self.modality == 'Flow':
            print("Converting the ImageNet model to a flow init model")
            self.base_model = self._construct_flow_model(self.base_model)
            print("Done. Flow model ready...")
        elif self.modality == 'RGBDiff':
            print("Converting the ImageNet model to RGB+Diff init model")
            self.base_model = self._construct_diff_model(self.base_model)
            print("Done. RGBDiff model ready.")

        # 初始化共识模块
        self.consensus = ConsensusModule(consensus_type)

        # 如果不在共识模块前进行Softmax操作,则添加Softmax层
        if not self.before_softmax:
            self.softmax = nn.Softmax()

        # 启用部分批量归一化
        self._enable_pbn = partial_bn
        if partial_bn:
            self.partialBN(True)

    def _prepare_tsn(self, num_class):
        """
        准备TSN模型的全连接层。

        参数:
        num_class (int): 分类的类别数量。

        返回:
        int: 特征维度。
        """
        # 获取基础模型最后一层的输入特征维度
        feature_dim = getattr(self.base_model, self.base_model.last_layer_name).in_features
        if self.dropout == 0:
            # 如果不使用Dropout,直接替换最后一层为全连接层
            setattr(self.base_model, self.base_model.last_layer_name, nn.Linear(feature_dim, num_class))
            self.new_fc = None
        else:
            # 如果使用Dropout,在最后一层前添加Dropout层,并添加新的全连接层
            setattr(self.base_model, self.base_model.last_layer_name, nn.Dropout(p=self.dropout))
            self.new_fc = nn.Linear(feature_dim, num_class)

        std = 0.001
        if self.new_fc is None:
            # 初始化最后一层的权重和偏置
            normal(getattr(self.base_model, self.base_model.last_layer_name).weight, 0, std)
            constant(getattr(self.base_model, self.base_model.last_layer_name).bias, 0)
        else:
            # 初始化新全连接层的权重和偏置
            normal(self.new_fc.weight, 0, std)
            constant(self.new_fc.bias, 0)
        return feature_dim

    def _prepare_base_model(self, base_model):
        """
        准备基础模型。

        参数:
        base_model (str): 基础模型的名称。
        """
        if 'resnet' in base_model or 'vgg' in base_model:
            # 如果是ResNet或VGG模型
            self.base_model = getattr(torchvision.models, base_model)(True)
            self.base_model.last_layer_name = 'fc'
            self.input_size = 224
            self.input_mean = [0.485, 0.456, 0.406]
            self.input_std = [0.229, 0.224, 0.225]

            if self.modality == 'Flow':
                # 如果是光流模态,修改输入均值和标准差
                self.input_mean = [0.5]
                self.input_std = [np.mean(self.input_std)]
            elif self.modality == 'RGBDiff':
                # 如果是RGB差分模态,修改输入均值和标准差
                self.input_mean = [0.485, 0.456, 0.406] + [0] * 3 * self.new_length
                self.input_std = self.input_std + [np.mean(self.input_std) * 2] * 3 * self.new_length
        elif base_model == 'BNInception':
            # 如果是BNInception模型
            import tf_model_zoo
            self.base_model = getattr(tf_model_zoo, base_model)()
            self.base_model.last_layer_name = 'fc'
            self.input_size = 224
            self.input_mean = [104, 117, 128]
            self.input_std = [1]

            if self.modality == 'Flow':
                # 如果是光流模态,修改输入均值
                self.input_mean = [128]
            elif self.modality == 'RGBDiff':
                # 如果是RGB差分模态,修改输入均值
                self.input_mean = self.input_mean * (1 + self.new_length)

        elif 'inception' in base_model:
            # 如果是Inception模型
            import tf_model_zoo
            self.base_model = getattr(tf_model_zoo, base_model)()
            self.base_model.last_layer_name = 'classif'
            self.input_size = 299
            self.input_mean = [0.5]
            self.input_std = [0.5]
        else:
            # 如果是未知的基础模型,抛出错误
            raise ValueError('Unknown base model: {}'.format(base_model))

    def train(self, mode=True):
        """
        重写默认的train()方法,冻结部分批量归一化层的参数。

        参数:
        mode (bool): 是否处于训练模式,默认为 True。

        返回:
        TSN: 模型实例。
        """
        super(TSN, self).train(mode)
        count = 0
        if self._enable_pbn:
            print("Freezing BatchNorm2D except the first one.")
            for m in self.base_model.modules():
                if isinstance(m, nn.BatchNorm2d):
                    count += 1
                    if count >= (2 if self._enable_pbn else 1):
                        # 冻结除第一个批量归一化层外的其他批量归一化层
                        m.eval()
                        # 关闭梯度更新
                        m.weight.requires_grad = False
                        m.bias.requires_grad = False
        return self

    def partialBN(self, enable):
        """
        启用或禁用部分批量归一化。

        参数:
        enable (bool): 是否启用部分批量归一化。
        """
        self._enable_pbn = enable

    def get_optim_policies(self):
        """
        获取优化策略,为不同类型的参数设置不同的学习率和衰减率。

        返回:
        list: 优化策略列表。
        """
        first_conv_weight = []
        first_conv_bias = []
        normal_weight = []
        normal_bias = []
        bn = []

        conv_cnt = 0
        bn_cnt = 0
        for m in self.modules():
            if isinstance(m, torch.nn.Conv2d) or isinstance(m, torch.nn.Conv1d):
                # 处理卷积层参数
                ps = list(m.parameters())
                conv_cnt += 1
                if conv_cnt == 1:
                    # 第一个卷积层的权重
                    first_conv_weight.append(ps[0])
                    if len(ps) == 2:
                        # 第一个卷积层的偏置
                        first_conv_bias.append(ps[1])
                else:
                    # 其他卷积层的权重
                    normal_weight.append(ps[0])
                    if len(ps) == 2:
                        # 其他卷积层的偏置
                        normal_bias.append(ps[1])
            elif isinstance(m, torch.nn.Linear):
                # 处理全连接层参数
                ps = list(m.parameters())
                # 全连接层的权重
                normal_weight.append(ps[0])
                if len(ps) == 2:
                    # 全连接层的偏置
                    normal_bias.append(ps[1])
            elif isinstance(m, torch.nn.BatchNorm1d):
                # 处理一维批量归一化层参数
                bn.extend(list(m.parameters()))
            elif isinstance(m, torch.nn.BatchNorm2d):
                # 处理二维批量归一化层参数
                bn_cnt += 1
                # 除冻结的批量归一化层外的参数
                if not self._enable_pbn or bn_cnt == 1:
                    bn.extend(list(m.parameters()))
            elif len(m._modules) == 0:
                if len(list(m.parameters())) > 0:
                    # 如果有新的原子模块类型,抛出错误
                    raise ValueError("New atomic module type: {}. Need to give it a learning policy".format(type(m)))

        return [
            {'params': first_conv_weight, 'lr_mult': 5 if self.modality == 'Flow' else 1, 'decay_mult': 1,
             'name': "first_conv_weight"},
            {'params': first_conv_bias, 'lr_mult': 10 if self.modality == 'Flow' else 2, 'decay_mult': 0,
             'name': "first_conv_bias"},
            {'params': normal_weight, 'lr_mult': 1, 'decay_mult': 1,
             'name': "normal_weight"},
            {'params': normal_bias, 'lr_mult': 2, 'decay_mult': 0,
             'name': "normal_bias"},
            {'params': bn, 'lr_mult': 1, 'decay_mult': 0,
             'name': "BN scale/shift"},
        ]

    def forward(self, input):
        """
        前向传播函数。

        参数:
        input (torch.Tensor): 输入张量。

        返回:
        torch.Tensor: 输出张量。
        """
        # 根据模态计算每个分段的输入长度
        sample_len = (3 if self.modality == "RGB" else 2) * self.new_length

        if self.modality == 'RGBDiff':
            # 如果是RGB差分模态,计算差分
            sample_len = 3 * self.new_length
            input = self._get_diff(input)

        # 调整输入形状并通过基础模型
        base_out = self.base_model(input.view((-1, sample_len) + input.size()[-2:]))

        if self.dropout > 0:
            # 如果使用Dropout,通过新的全连接层
            base_out = self.new_fc(base_out)

        if not self.before_softmax:
            # 如果不在共识模块前进行Softmax操作,进行Softmax操作
            base_out = self.softmax(base_out)
        if self.reshape:
            # 调整输出形状
            base_out = base_out.view((-1, self.num_segments) + base_out.size()[1:])

        # 通过共识模块
        output = self.consensus(base_out)
        return output.squeeze(1)

    def _get_diff(self, input, keep_rgb=False):
        """
        计算RGB差分。

        参数:
        input (torch.Tensor): 输入张量。
        keep_rgb (bool): 是否保留RGB通道,默认为 False。

        返回:
        torch.Tensor: 差分后的张量。
        """
        # 根据模态确定输入通道数
        input_c = 3 if self.modality in ["RGB", "RGBDiff"] else 2
        # 调整输入形状
        input_view = input.view((-1, self.num_segments, self.new_length + 1, input_c,) + input.size()[2:])
        if keep_rgb:
            # 如果保留RGB通道,克隆输入
            new_data = input_view.clone()
        else:
            # 否则,截取部分输入
            new_data = input_view[:, :, 1:, :, :, :].clone()

        for x in reversed(list(range(1, self.new_length + 1))):
            if keep_rgb:
                # 如果保留RGB通道,计算差分
                new_data[:, :, x, :, :, :] = input_view[:, :, x, :, :, :] - input_view[:, :, x - 1, :, :, :]
            else:
                # 否则,计算差分
                new_data[:, :, x - 1, :, :, :] = input_view[:, :, x, :, :, :] - input_view[:, :, x - 1, :, :, :]

        return new_data

    def _construct_flow_model(self, base_model):
        """
        将基础模型转换为光流模型。

        参数:
        base_model (torch.nn.Module): 基础模型。

        返回:
        torch.nn.Module: 转换后的光流模型。
        """
        # 获取基础模型的所有模块
        modules = list(self.base_model.modules())
        # 找到第一个卷积层的索引
        first_conv_idx = list(filter(lambda x: isinstance(modules[x], nn.Conv2d), list(range(len(modules)))))[0]
        conv_layer = modules[first_conv_idx]
        container = modules[first_conv_idx - 1]

        # 克隆第一个卷积层的参数
        params = [x.clone() for x in conv_layer.parameters()]
        kernel_size = params[0].size()
        # 计算新的卷积核大小
        new_kernel_size = kernel_size[:1] + (2 * self.new_length, ) + kernel_size[2:]
        # 计算新的卷积核
        new_kernels = params[0].data.mean(dim=1, keepdim=True).expand(new_kernel_size).contiguous()

        # 创建新的卷积层
        new_conv = nn.Conv2d(2 * self.new_length, conv_layer.out_channels,
                             conv_layer.kernel_size, conv_layer.stride, conv_layer.padding,
                             bias=True if len(params) == 2 else False)
        new_conv.weight.data = new_kernels
        if len(params) == 2:
            # 如果有偏置,设置新卷积层的偏置
            new_conv.bias.data = params[1].data
        # 获取卷积层的名称
        layer_name = list(container.state_dict().keys())[0][:-7]

        # 替换第一个卷积层
        setattr(container, layer_name, new_conv)
        return base_model

    def _construct_diff_model(self, base_model, keep_rgb=False):
        """
        将基础模型转换为RGB差分模型。

        参数:
        base_model (torch.nn.Module): 基础模型。
        keep_rgb (bool): 是否保留RGB通道,默认为 False。

        返回:
        torch.nn.Module: 转换后的RGB差分模型。
        """
        # 获取基础模型的所有模块
        modules = list(self.base_model.modules())
        # 找到第一个卷积层的索引
        first_conv_idx = list(filter(lambda x: isinstance(modules[x], nn.Conv2d), list(range(len(modules)))))[0]
        conv_layer = modules[first_conv_idx]
        container = modules[first_conv_idx - 1]

        # 克隆第一个卷积层的参数
        params = [x.clone() for x in conv_layer.parameters()]
        kernel_size = params[0].size()
        if not keep_rgb:
            # 如果不保留RGB通道,计算新的卷积核大小和卷积核
            new_kernel_size = kernel_size[:1] + (3 * self.new_length,) + kernel_size[2:]

实验结果

与不同数据集上的最先进方法的对比:
在这里插入图片描述
可见,无论是三段还是七段TSN,其性能都优于其他方法。
使用平均池化聚合函数下对视频分段段数的实验结果:
在这里插入图片描述
可见,随着段数的增加,性能逐渐上升,但段数达到一定值后性能会达到饱和。

总结

时间分割网络(TSN)是一种高效的长时程视频动作识别框架,其核心通过分段稀疏采样策略解决传统方法在长视频建模中的计算冗余与覆盖不足问题。TSN将视频均匀划分为多个时间段,每个段随机选取一个短片段,利用共享权重的卷积网络提取片段特征,并通过聚合函数(如平均池化或Top-K池化)融合全局信息生成视频级预测。对于未修剪视频,TSN进一步引入多尺度时序窗口集成(M-TWI),通过多尺度滑动窗口和背景抑制策略提升鲁棒性。其优势在于计算成本与视频时长无关,支持实时推理,并在多个数据集上达到最优性能。然而,TSN依赖预训练模型初始化,且光流计算仍存在效率瓶颈。未来研究可聚焦于轻量化模态设计、端到端多模态融合优化,以及提升模型在复杂动态场景中的泛化能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

扩展域并查集

什么叫扩展域并查集 1 和 2是敌人,那么就把1好12链接起来:表示1和2是敌人 2和11链接起来也是这个道理 然后2 和3使敌人同理。 最后12连接了1 和 3,表名1 和 3 是 2 的敌人,1和3 就是朋友 1.P1892 [BalticOI 2003] 团伙 - 洛谷 #in…

【C#语言】C#同步与异步编程深度解析:让程序学会“一心多用“

文章目录 ⭐前言⭐一、同步编程:单线程的线性世界🌟1、寻找合适的对象✨1) 🌟7、设计应支持变化 ⭐二、异步编程:多任务的协奏曲⭐三、async/await工作原理揭秘⭐四、最佳实践与性能陷阱⭐五、异步编程适用场景⭐六、性能对比实测…

动态规划入门详解

动态规划(Dynamic Programming,简称DP)是一种算法思想,它将问题分解为更小的子问题,然后将子问题的解存起来,避免重复计算。 所以动态规划中每一个状态都是由上一个状态推导出来的,这一点就区别…

SOFABoot-09-模块隔离

前言 大家好,我是老马。 sofastack 其实出来很久了,第一次应该是在 2022 年左右开始关注,但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…

基于基于eFish-SBC-RK3576工控板的智慧城市边缘网关

此方案充分挖掘eFish-SBC-RK3576的硬件潜力,可快速复制到智慧园区、交通枢纽等场景。 方案亮点 ‌接口高密度‌:单板集成5GWiFi多路工业接口,减少扩展复杂度。‌AIoT融合‌:边缘端完成传感器数据聚合与AI推理,降低云端…

CSS基础知识一览

持续维护 选择器 display 常用属性 浮动 弹性布局

【免费】2000-2019年各省地方财政房产税数据

2000-2019年各省地方财政房产税数据 1、时间:2000-2019年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区、年份、地方财政房产税 4、范围:31省 5、指标说明:房产税是对个人和单位拥有的房产征收的一种…

车载以太网网络测试-21【传输层-DOIP协议-4】

目录 1 摘要2 DoIP entity status request/response(0x4001、0x4002)2.1 使用场景2.2 报文结构2.2.1 0x4001:DoIP entity status request2.2.2 0x4002:DoIP entity status response 3 Diagnostic power mode information request/…

Spring AI Alibaba ChatModel使用

一、对话模型(Chat Model)简介 1、对话模型(Chat Model) 对话模型(Chat Model)接收一系列消息(Message)作为输入,与模型 LLM 服务进行交互,并接收返回的聊天…

基于FPGA频率、幅度、相位可调的任意函数发生器(DDS)实现

基于FPGA实现频率、幅度、相位可调的DDS 1 摘要 直接数字合成器( DDS ) 是一种通过生成数字形式的时变信号并进行数模转换来产生模拟波形(通常为正弦波)的方法,它通过数字方式直接合成信号,而不是通过模拟信号生成技术。DDS主要被应用于信号生成、通信系统中的本振、函…

k8s高可用集群安装

一、安装负载均衡器 k8s负载均衡器 官方指南 1、准备三台机器 节点名称IPmaster-1192.168.1.11master-2192.168.1.12master-3192.168.1.13 2、在这三台机器分别安装haproxy和keepalived作为负载均衡器 # 安装haproxy sudo dnf install haproxy -y# 安装Keepalived sudo yum …

3DMAX曲线生成器插件CurveGenerator使用方法

1. 脚本功能简介 3DMAX曲线生成器插件CurveGenerator是一个用于 3ds Max 的样条线生成工具,用户可以通过简单的UI界面输入参数,快速生成多条样条线。每条样条线的高度值随机生成,且可以自定义以下参数: 顶点数量:每条…

六十天前端强化训练之第二十六天之Vue Router 动态路由参数大师级详解

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗,谢谢大佬! 目录 一、知识讲解 1. Vue Router 核心概念 2. 动态路由参数原理 3. 参数传递方案对比 二、核心代码示例 1. 完整路由配置 2. 参数接收组件 3. 导航操作示例 三、实现效果示…

Model Context Protocol:下一代AI系统集成范式革命

在2023年全球AI工程化报告中,开发者面临的核心痛点排名前三的分别是:模型与业务系统集成复杂度(58%)、上下文管理碎片化(42%)、工具调用标准化缺失(37%)。传统API集成模式在对接大语言模型时暴露明显短板:RESTful接口无法承载动态上下文,GraphQL缺乏工具编排能力,gR…

Java多线程与高并发专题——Future 是什么?

引入 在上一篇Callable 和 Runnable 的不同?的最后,我们有提到和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable…

DeepSeek本地搭建

1. 软件下载安装 Miniconda Miniconda下载地址 选择对应的版本下载,此处下载如下版本 Python 3.10 conda 25.1.1 安装完成后,配置环境变量,打开cmd命令窗口验证 Python Python的版本为 3.10 PyTorch PyTorch下载地址 后面通过命令下…

维普AIGC降重方法有哪些?

在学术写作和论文创作中,重复率过高是许多人面临的一大难题。随着科技的发展,维普 AIGC 为我们提供了一系列有效的降重方法。那么,维普AIGC降重方法有哪些呢?接下来就为大家详细介绍。 语义理解与改写 维普 AIGC 具备强大的语义理…

南京审计大学:《 面向工程审计行业的DeepSeek大模型应用指南》.pdf(免费下载)

大家好,我是吾鳴。 今天吾鳴要给大家分享的是由南京审计大学出品的《面向工程审计行业的DeepSeek大模型应用指南》,这份报告与《面向审计行业DeepSeek大模型操作指南》不同,这份报告更多的讲述DeepSeek怎么与工程审计行业结合,应该…

【前端】Canvas画布实现在线的唇膏换色功能

【前端】Canvas画布实现在线的唇膏换色功能 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 【前端】Canvas画布实现在线的唇膏换色功能背景概述以下是我们的实现方法!第一步 — 找…

arcgispro加载在线地图

World_Imagery (MapServer)https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer添加arcgis server WMTS 服务 by xdcxdc.at xdc的个人站点。博客请转至 http://i.xdc.at/ http://xdc.at/map/wmts 添加WMTS服务器