首页
/ TensorFlow Models CVRL 实战指南:时空视频对比学习(video_ssl)的训练与线性评估

TensorFlow Models CVRL 实战指南:时空视频对比学习(video_ssl)的训练与线性评估

2026-09-06 22:07:08作者:温玫谨Lighthearted

本篇基于 TensorFlow 官方模型仓库中 official/projects/video_ssl 项目的 README 及其配套源码展开,完整讲解 CVRL(Contrastive Video Representation Learning,对比视频表示学习)方法在 TF2 中的落地实现:从时空对比损失、双视角视频增强数据管线,到 TPU 上的预训练配置与冻结骨干的线性评估流程。读完本文,你可以理解 CVRL 的自监督训练机制如何在未标注视频上学习时空表征,并能基于仓库中的实验配置复现 Kinetics-400/600 上的预训练与 top-1 线性分类评估。

一、方法概述:什么是 CVRL

CVRL 是一种自监督时空视频表示学习方法,其核心思想是:从同一段短视频中抽取两个经过不同数据增强的片段(view),用对比损失将它们在嵌入空间中拉近,同时把来自不同视频的片段推远。这样无需任何人工标注,模型仅凭"两个片段是否来自同一段视频"这一弱监督信号,即可学到可迁移的时空视觉表征。据项目 README 描述,该方法显著缩小了无监督与有监督视频表示学习之间的差距,并大幅收窄了与当时有监督 SOTA 的精度差。

该目录是 CVPR 2021 论文 Spatiotemporal Contrastive Video Representation Learning(arXiv:2008.03800)的官方 TF2 实现,仓库同时提供论文中的实验结果图与预训练模型。项目的两个核心工作流分别是:

  1. 预训练(pretrain):在未标注 Kinetics 视频上用对比损失训练 R3D-50 骨干 + 投影头;
  2. 线性评估(linear eval):冻结预训练好的骨干,只训练一个线性分类头,用于度量所学表征的质量。

目录结构

文件 / 目录 作用
train.py 训练驱动入口,解析 exp_config 与 yaml 实验配置
configs/video_ssl.py 模型、损失、数据的配置定义与 4 个实验配置工厂
configs/experiments/cvrl_pretrain_k600_200ep.yaml Kinetics-600 对比预训练完整配置
configs/experiments/cvrl_linear_eval_k600.yaml Kinetics-600 线性评估完整配置
modeling/video_ssl_model.py VideoSSLModel:骨干 + MLP 投影头
losses/losses.py InfoNCE 对比损失与 TPU 跨副本拼接
dataloaders/video_ssl_input.py 双视角视频采样、裁剪与 SSL 增强
ops/video_ssl_preprocess_ops.py 时序一致的 3D 颜色抖动、高斯模糊、Solarization
tasks/pretrain.py 预训练任务:前向、对比损失、训练步
tasks/linear_eval.py 线性评估任务:加载骨干 checkpoint 并冻结
video_ssl.ipynb 使用预训练模型提取特征的示例 Notebook

二、模型架构:骨干网络之上的对比投影头

预训练模型由 configs/video_ssl.py 中的 VideoSSLModel 配置类驱动,关键字段如下:

配置项 默认值 含义
normalize_feature False 是否对骨干池化后的特征做 L2 归一化(线性评估时置 True
hidden_dim 2048 MLP 隐层宽度
hidden_layer_num 3 MLP 隐层数量(预训练为 3 层,线性评估置 0,即退化为单层投影)
projection_dim 128 投影头输出维度(对比嵌入维度)
hidden_norm_activation NormActivation(use_sync_bn=False, norm_momentum=0.997, norm_epsilon=1e-5) MLP 中的归一化 + 激活配置,TPU 上通常开启 SyncBN

模型构建实现见 modeling/video_ssl_model.pyVideoSSLModel 的前向结构是:

  1. 3D 骨干(R3D-50):通过 backbones.factory.build_backbone 依据 backbone 配置构建(对应 yaml 中的 resnet_3d 块,1x 变体即各阶段时序卷积核全为 1,见 configs/experiments/cvrl_pretrain_k600_200ep.yamlblock_specs);
  2. GlobalAveragePooling3D 得到视频级特征;
  3. 可选的 tf.nn.l2_normalizenormalize_feature=True 时启用,见 video_ssl_model.py);
  4. MLP 隐层Dense(hidden_dim) → BatchNormalization/SyncBatchNormalization → 激活hidden_layer_num 组(TPU 多副本场景使用 SyncBatchNormalization 保证归一化统计跨副本一致);
  5. 投影头Dense(projection_dim) 输出最终用于对比学习的 128 维嵌入。

一个值得注意的实现细节:构建函数 build_video_ssl_pretrain_model 中有一条断言(video_ssl_model.py),要求 MLP 中归一化层的 use_sync_bn 与骨干保持一致,避免骨干与投影头的归一化行为在分布式训练下分叉。

模型通过工厂注册为 video_ssl_modelvideo_ssl_model.py),因此在 yaml 中只需写 model_type: 'video_ssl_model' 即可由框架实例化。

三、对比损失:温度化 InfoNCE 与 TPU 跨副本负样本

损失定义在 losses/losses.pycontrastive_loss 中,是标准的对称 InfoNCE 实现,核心逻辑:

  • 正负样本划分:输入 hidden 在 batch 维上按 2 分割为 hidden1 / hidden2(两个视角)。若 normalize_hidden=True,先做 L2 归一化(配置默认开启,见 configs/video_ssl.pySSLLosses)。
  • 温度缩放:所有相似度矩阵除以温度 temperature(配置默认 0.1)。
  • 对称损失loss_a = HCE(labels, [logits_ab, logits_aa])loss_b = HCE(labels, [logits_ba, logits_bb]),总损失为两者之和。对角线(自身)位置用 -1e9 掩码排除。
  • TPU 跨副本扩展负样本池:多副本时通过 tpu_cross_replica_concatlosses.py)用 tf.scatter_nd + all_reduce(SUM) 技巧把各 TPU core 的嵌入拼成一个全局大 batch,使每个样本拥有 2 × global_batch_size 个候选对比对象。这也是为什么配置中全局 batch size 直接决定负样本规模。
  • L2 权重衰减:对所有名字含 kernel 的可训练变量加 weight_decay * Σ l2_loss,默认 1e-6

损失函数还顺带返回监控指标 contrast_acc(对比分类准确率)与 contrast_entropy,预训练任务中注册为流式指标(tasks/pretrain.py),训练时可在日志中直接观察对比学习收敛情况。

损失由 VideoSSLPretrainTask.build_losses 接入(tasks/pretrain.py),train_step 中先取 tf.distribute.get_strategy().num_replicas_in_sync 作为副本数传入,混合精度下还会把模型输出统一 cast 到 float32 再算损失(tasks/pretrain.py),保证 InfoNCE 数值稳定。

四、数据管线:双视角采样与时空一致增强

自监督预训练的数据增强是方法的关键,实现集中在 dataloaders/video_ssl_input.py_process_image 中,训练路径(is_training=True, is_ssl=True)依次为:

  1. 双片段时序采样:调用 video_ssl_preprocess_ops.sample_ssl_sequence,从"线性递减分布"中采出同一段视频中的两个子片段(而非完全随机),保证两个 view 保留一定时序重叠,这是"时空"对比学习的核心设计之一;
  2. 空间增强:两个 view 各自独立做随机尺度裁剪缩放(aspect ratio 均匀采自 [0.5, 2.0],面积采自 [0.3, 1.0]random_crop_resize)与随机左右翻转;
  3. 时序一致的颜色抖动random_color_jitter_3d 对整个片段共享同一组亮度/对比度/饱和度/色相参数,确保增强在时间维度上保持一致(video_ssl_preprocess_ops.py);
  4. 时序一致的高斯模糊 + Solarization:两个 view 分别以不同概率参数做 random_blur,仅第二个 view 额外做 random_solarization
  5. 归一化:像素值统一映射回 [0, 1]zero_centering_image=True 时映射到 [-1, 1])。

后处理 _postprocess_imagevideo_ssl_input.py)会把两个 view 从时间维拆到 batch 维(batch 变为 2×),这与 contrastive_losstf.split(hidden, 2, axis=0) 的约定严格对应。

评估路径则走经典的 10 clip × 3 crop 增强:num_test_clips=10(线性间隔采样 10 个片段)、num_test_crops=3(三分裁剪),同样在 batch 维展开。

五、配置与预训练流程

实验注册

configs/video_ssl.py 通过 exp_factory.register_config_factory 注册了 4 个实验:

实验名 数据集 用途
video_ssl_pretrain_kinetics400 Kinetics-400 对比预训练(16 帧 × 224²,temporal_stride=2
video_ssl_pretrain_kinetics600 Kinetics-600 对比预训练
video_ssl_linear_eval_kinetics400 Kinetics-400 线性评估(训练 32 帧 × 224²,验证 32 帧 × 256²,normalize_feature=Truehidden_layer_num=0projection_dim=600
video_ssl_linear_eval_kinetics600 Kinetics-600 线性评估

其中线性评估配置还显式设置了 num_test_clips=10num_test_crops=3configs/video_ssl.py),与论文中的测试增强协议一致。

预训练配置详解

cvrl_pretrain_k600_200ep.yaml 给出了一份面向 TPU 的完整可运行配置,关键参数:

runtime:
  distribution_strategy: 'tpu'
  mixed_precision_dtype: 'bfloat16'
task:
  model:
    dropout_rate: 1.0            # 配合 L2 衰减使用
    norm_activation:
      use_sync_bn: true          # TPU 下骨干启用同步 BN
    hidden_norm_activation:
      use_sync_bn: true
    backbone:
      resnet_3d:
        model_id: 50
        stem_conv_temporal_kernel_size: 5
        stem_conv_temporal_stride: 2
        stem_pool_temporal_stride: 1
        # 1x 变体:所有残差块 temporal_kernel_sizes 全为 1
  train_data:
    name: kinetics600
    feature_shape: (16, 224, 224, 3)   # 每 clip 16 帧(双 view 共 32 帧)
    temporal_stride: 2
    global_batch_size: 1024           # 负样本池 = 2048 个嵌入
    dtype: 'bfloat16'
  losses:
    l2_weight_decay: 0.000001
trainer:
  optimizer_config:
    learning_rate:
      cosine:
        initial_learning_rate: 0.32
        decay_steps: 71488
    optimizer:
      sgd:
        nesterov: false
    warmup:
      linear:
        warmup_steps: 1787
  train_steps: 71488
  steps_per_loop: 100

要点解读:

  • feature_shape 的时间维为 16,是因为数据管线会把每视频采出的两个 16 帧 clip 沿时间维拼成 32 帧输入,再在后处理中拆到 batch 维;
  • global_batch_size=1024 意味着对比损失中每个样本面对约 2048 个候选(2 × global_batch_size),是 CVRL 大负样本策略的直接体现;
  • 学习率 0.32 + cosine 衰减 + 线性 warmup 1787 步,对应 200 epoch 的 K600 训练量(train_steps=71488),整体遵循"大 batch + 大学习率"的视觉对比学习惯例。

启动命令

训练入口 train.py 基于官方 official.core.train_lib 框架:mainparse_configuration(FLAGS) 解析出实验参数,task_factory.get_tasktask 配置类取出对应任务,最后 run_experiment 驱动训练。按仓库中其他项目的通用调用方式,预训练命令形如:

python official/projects/video_ssl/train.py \
  --exp_config=video_ssl_pretrain_kinetics600 \
  --config_file=official/projects/video_ssl/configs/experiments/cvrl_pretrain_k600_200ep.yaml \
  --model_dir=/tmp/cvrl_pretrain_k600 \
  --mode=train

其中 --exp_config 指定上面注册的实验名,--config_file 在其基础上覆盖超参数(如学习率、步数、batch size)。train_and_eval 模式下入口会断言训练/验证的 feature_shape 一致(train.py),因此对比预训练通常使用纯 train 模式。

六、线性评估:只训练线性头的表征测试

线性评估是检验自监督表征质量的标准协议:冻结骨干、仅训练其上的线性分类层。仓库实现见 tasks/linear_eval.py

  • 加载骨干权重initializeinit_checkpoint 恢复,且仅当 init_checkpoint_modules == 'backbone' 时用 tf.train.Checkpoint(backbone=model.backbone) 恢复骨干参数(linear_eval.py)。因此 yaml 中需配置:
task:
  init_checkpoint: 'r3d_1x_k600_800ep_backbone-1'
  init_checkpoint_modules: 'backbone'
  model:
    dropout_rate: 1.0
    backbone:
      resnet_3d:   # 与预训练完全相同的 R3D-50 1x 结构
    train_data:
      feature_shape: (32, 224, 224, 3)   # 评估阶段 32 帧
    losses:
      l2_weight_decay: 0.0              # 线性头不使用权重衰减
  • 冻结骨干train_step 每次迭代开头执行 model.backbone.trainable = False,梯度只流向线性头(linear_eval.py);
  • 测试增强:验证集使用 num_test_clips=10num_test_crops=3,即 10 个片段 × 3 个裁剪的 test-time 增强,验证 batch 64、关闭 drop_remainder

线性评估实验配置 cvrl_linear_eval_k600.yaml 中,线性头学习率高达 32.0(cosine 衰减 35744 步),这是线性探针的典型做法——大学习率、短训练;validation_interval: 100 用于周期性记录 top-1 精度。运行方式与预训练相同,将 --exp_config 换为 video_ssl_linear_eval_kinetics600 即可。

七、官方预训练检查点与结果

README 提供了在 Kinetics-400/600 无标签视频上从零(随机初始化)训练的 R3D-50 (1x) 检查点,以及论文使用的 ImageNet 2D ResNet-50 膨胀基线("ImageNet inflated",架构同为 R3D-50,权重由 ImageNet 预训练的 2D ResNet-50 膨胀而来)。各检查点对应的 K400/K600 top-1 线性评估精度如下(参数 31.7M):

模型 数据集 Epochs K400 Linear Eval. K600 Linear Eval.
R3D-50 (1x) ImageNet(膨胀基线) - 53.5% 54.7%
R3D-50 (1x) Kinetics-400 200 63.8% -
R3D-50 (1x) Kinetics-400 800 66.1% -
R3D-50 (1x) Kinetics-600 800 68.5% 70.4%

检查点为约 127 MB 的 tar.gz 文件(如 r3d_1x_k600_800ep.tar.gz),下载链接列于 README 的 "Pre-trained Model Checkpoints" 表格中。解压后在评估 yaml 的 init_checkpoint 中填入对应路径即可复用。Kinetics-600 top-1 线性精度的对比图(CVRL 与有监督 SOTA 的差距收敛情况)同样见 README 中 "Experimental Results" 一节。

此外,video_ssl.ipynb 展示了如何用加载好的预训练模型对视频提取特征,适合快速上手特征复用场景。

八、小结:复现要点清单

  1. 双视角来自同一视频:两个 clip 由 sample_ssl_sequence 从线性递减分布采样,空间增强独立、时间增强(颜色抖动/模糊)片段级一致;
  2. 对比损失参数:温度 0.1、嵌入 L2 归一化、对称 InfoNCE、TPU 跨副本拼接负样本池,global_batch_size=1024 决定负样本规模;
  3. 预训练配置:R3D-50 1x + 3 层 2048 隐层 MLP + 128 维投影头,SGD 0.32 大学习率 + cosine + warmup,bfloat16 混合精度;
  4. 线性评估协议:仅恢复并冻结骨干(init_checkpoint_modules: 'backbone'),线性头大学习率短训练,验证用 10 clip × 3 crop;
  5. 所有路径均可在本仓库内对照:配置在 official/projects/video_ssl/configs,实现在 modeling / losses / dataloaders / tasks

论文引用信息(来自 README):

@inproceedings{qian2021spatiotemporal,
  title={Spatiotemporal contrastive video representation learning},
  author={Qian, Rui and Meng, Tianjian and Gong, Boqing and Yang, Ming-Hsuan and Wang, Huisheng and Belongie, Serge and Cui, Yin},
  booktitle={CVPR},
  year={2021}
}
登录后查看全文
热门项目推荐
相关项目推荐