引言

视频自监督学习旨在在没有人工标注的情况下学习有效的视频表示。与图像自监督学习相比,视频自监督学习可以利用额外的时间维度信息,学习到丰富的时序动态和运动模式表示。

本文系统介绍视频自监督学习的核心方法,包括视频对比学习、视频预测与重建、掩码视频建模、时间一致性建模等方向,以及与图像预训练的结合策略。


视频自监督学习概述

与图像SSL的区别

视频自监督学习相比图像自监督学习具有独特的优势和挑战:

方面图像SSL视频SSL
数据属性静态2D动态3D(时间+空间)
可用信息空间上下文、纹理、颜色运动、光流、时间顺序
** pretext task**旋转、拼图、对比时间顺序、跟踪、预测
挑战语义不变性时序一致性
应用分类、检测动作识别、跟踪、分割

额外的时间维度信息

视频包含丰富的时序信息:

  1. 运动信息:物体如何在时间中移动
  2. 时间顺序:事件发生的先后关系
  3. 周期性:重复模式(如走路、跑步)
  4. 因果关系:什么导致什么发生

Pretext Task 设计

视频SSL的代理任务(Pretext Task)设计原则:

代理任务设计:
┌─────────────────────────────────────────────────────┐
│  1. 利用时间维度                                   │
│     - 时间顺序验证                                  │
│     - 帧同步/异步判断                               │
│     - 周期检测                                      │
├─────────────────────────────────────────────────────┤
│  2. 利用运动信息                                   │
│     - 光流预测                                      │
│     - 运动分割                                      │
│     - 跟踪预测                                      │
├─────────────────────────────────────────────────────┤
│  3. 视频重建                                      │
│     - 帧重建                                      │
│     - 未来帧预测                                   │
│     - 掩码重建                                     │
├─────────────────────────────────────────────────────┤
│  4. 对比学习                                       │
│     - 时间增强对比                                  │
│     - 跨视图对比                                    │
│     - 多模态对比                                   │
└─────────────────────────────────────────────────────┘

视频对比学习

核心思想

视频对比学习的核心是让同一视频的不同视图(如不同时间片段)具有相似的表示,而不同视频的表示要有区别

这与图像对比学习(如SimCLR、MoCo)类似,但需要特别处理时间维度:

class VideoContrastiveLoss(nn.Module):
    """
    视频对比损失
    """
    def __init__(self, temperature=0.07):
        super().__init__()
        self.tau = temperature
    
    def forward(self, z1, z2):
        """
        z1, z2: 同一视频的两个不同时间视图的表示
        """
        # 归一化
        z1 = F.normalize(z1, dim=-1)
        z2 = F.normalize(z2, dim=-1)
        
        # 计算相似度矩阵
        sim = torch.matmul(z1, z2.T) / self.tau
        
        # 对角线是正样本对
        labels = torch.arange(len(z1), device=z1.device)
        
        # 对称损失
        loss = F.cross_entropy(sim, labels) + \
               F.cross_entropy(sim.T, labels)
        
        return loss

CoCLR

CoCLR(Cross-Contrastive Representation Learning)1 是一种视频对比学习方法,同时利用视觉和运动信息:

class CoCLR(nn.Module):
    """
    CoCLR: 跨模态对比学习
    """
    def __init__(self, vision_encoder, flow_encoder):
        super().__init__()
        self.vision_encoder = vision_encoder
        self.flow_encoder = flow_encoder
        
        # 投影头
        self.vision_proj = nn.Linear(vision_dim, proj_dim)
        self.flow_proj = nn.Linear(flow_dim, proj_dim)
    
    def forward(self, frames, flows):
        # 编码视频帧
        f_v = self.vision_encoder(frames)
        f_v = self.vision_proj(f_v)
        
        # 编码光流
        f_f = self.flow_encoder(flows)
        f_f = self.flow_proj(f_f)
        
        # 跨模态对比损失
        loss_v2f = self.contrastive_loss(f_v, f_f)
        loss_f2v = self.contrastive_loss(f_f, f_v)
        
        return (loss_v2f + loss_f2v) / 2
    
    def contrastive_loss(self, z1, z2):
        # 与正样本对比,与其他负样本区分
        ...

CoCLR的关键设计

  1. 双流架构:视觉流 + 光流流
  2. 跨模态对比:视觉表示与光流表示互相预测
  3. 在线对比:不需要额外的队列或动量更新

时间增强的重要性

视频对比学习中的时间增强策略对性能至关重要:

def temporal_augmentations(video, num_clips=2):
    """
    时间增强策略
    """
    T, C, H, W = video.shape
    
    clips = []
    for _ in range(num_clips):
        # 策略1: 随机时间裁剪
        start = random.randint(0, T - clip_length)
        clip1 = video[start:start+clip_length]
        
        # 策略2: 不同的采样率
        clip2 = video[:, :, :, :, ::2]  # 跳帧
        
        # 策略3: 时间反转
        if random.random() > 0.5:
            clip2 = torch.flip(clip2, dims=[0])
        
        clips.append((clip1, clip2))
    
    return clips

关键发现

  • 时间裁剪增强比颜色增强更重要
  • 不同采样率提供互补的时间信息
  • 时间反转是有效的数据增强

SimCLR-Video

SimCLR可以自然扩展到视频领域:

class SimCLR_Video(nn.Module):
    """
    SimCLR扩展到视频
    """
    def __init__(self, encoder, projection_dim=128):
        super().__init__()
        self.encoder = encoder
        self.projection_head = nn.Sequential(
            nn.Linear(embed_dim, 256),
            nn.ReLU(),
            nn.Linear(256, projection_dim)
        )
    
    def forward(self, video, t1, t2):
        """
        video: 原始视频
        t1, t2: 两个时间视图的采样位置
        """
        # 采样两个时间视图
        v1 = video[:, t1:t1+clip_len]
        v2 = video[:, t2:t2+clip_len]
        
        # 编码
        h1 = self.encoder(v1)
        h2 = self.encoder(v2)
        
        # 投影
        z1 = self.projection_head(h1)
        z2 = self.projection_head(h2)
        
        # NT-Xent损失
        return self.nt_xent_loss(z1, z2)
    
    def nt_xent_loss(self, z1, z2, tau=0.5):
        z = torch.cat([z1, z2], dim=0)
        sim = torch.mm(z, z.T) / tau
        sim.fill_diagonal_(float('-inf'))
        
        labels = torch.arange(len(z), device=z.device)
        loss = F.cross_entropy(sim, labels)
        
        return loss

视频预测与重建

视频预测

视频预测任务要求模型根据过去的视频帧预测未来帧:

class VideoPredictor(nn.Module):
    """
    视频预测模型
    """
    def __init__(self, encoder, decoder, latent_dim):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.prior = nn.Sequential(
            nn.Linear(encoder_dim, latent_dim),
            nn.ReLU(),
            nn.Linear(latent_dim, latent_dim)
        )
    
    def forward(self, past_frames, num_future=8):
        """
        past_frames: [B, T, C, H, W]
        """
        # 编码过去帧
        h = self.encoder(past_frames)
        
        # 预测未来潜变量
        z = self.prior(h)
        
        # 解码预测未来帧
        future_frames = self.decoder(z, num_future)
        
        return future_frames

VideoGPT

VideoGPT2 将GPT思想应用到视频生成:

class VideoGPT(nn.Module):
    """
    VideoGPT: 使用GPT架构生成视频
    """
    def __init__(self, vocab_size, embed_dim, num_heads, num_layers):
        super().__init__()
        
        # VQ-VAE风格的离散化
        self.vq_layer = VectorQuantizer(...)
        
        # Transformer解码器
        self.transformer = nn.TransformerDecoder(
            nn.TransformerDecoderLayer(
                d_model=embed_dim,
                nhead=num_heads
            ),
            num_layers=num_layers
        )
        
        # 位置编码
        self.pos_embedding = nn.Parameter(
            torch.randn(1, max_frames, embed_dim)
        )
    
    def forward(self, video, mask=None):
        # VQ编码
        indices = self.vq_layer.encode(video)
        
        # 移除最后一帧作为目标
        target = indices[:, 1:]
        indices = indices[:, :-1]
        
        # 添加位置编码
        indices = indices + self.pos_embedding[:, :indices.size(1)]
        
        # 自回归生成
        output = self.transformer(indices, indices, tgt_mask=mask)
        
        return output, target

掩码重建目标

掩码视频建模的重建目标选择:

目标类型优点缺点
像素值简单直接高维、细节过多
光流关注运动需要额外监督
HOG特征低维、鲁棒人工设计
离散token高效、表示紧凑需要额外VAE
特征统计低维可能有信息损失

Masked Video Modeling 掩码视频建模

MAV (MAsked Video)

MAV是一种类似MAE的掩码视频建模方法:

class MaskedVideoMAE(nn.Module):
    """
    MAV: Masked Video Autoencoder
    """
    def __init__(self, encoder, decoder, mask_ratio=0.75):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.mask_ratio = mask_ratio
    
    def forward(self, video):
        """
        video: [B, T, C, H, W]
        """
        B, T, C, H, W = video.shape
        
        # 生成随机掩码
        mask = self.generate_mask(B, T, self.mask_ratio)
        
        # 编码可见patch
        visible_video = video * mask.unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
        features = self.encoder(visible_video)
        
        # 解码重建
        reconstructed = self.decoder(features, mask)
        
        # 计算重建损失 (仅在被掩码的位置)
        loss = self.compute_loss(reconstructed, video, mask)
        
        return loss
    
    def generate_mask(self, B, T, mask_ratio):
        """生成随机掩码"""
        N = T  # 简化:每帧作为一个patch
        num_mask = int(N * mask_ratio)
        
        noise = torch.rand(B, N)
        ids_shuffle = torch.argsort(noise, dim=1)
        ids_restore = torch.argsort(ids_shuffle, dim=1)
        
        ids_keep = ids_shuffle[:, num_mask:]
        mask = torch.ones(B, N)
        mask[:, :num_mask] = 0
        mask = torch.gather(mask, dim=1, index=ids_restore)
        
        return mask.bool()

VideoMAE

VideoMAE3 是掩码自编码器在视频领域的成功应用:

VideoMAE流程:
┌──────────────────────────────────────────┐
│  输入视频: [B, T, H, W, C]               │
│  3D Tubelet Embedding                    │
│  ↓                                      │
│  随机掩码 (75-90% 掩码率)                │
│  ↓                                      │
│  Transformer编码器 (仅处理可见patch)       │
│  ↓                                      │
│  Transformer解码器                       │
│  ↓                                      │
│  像素级重建                              │
└──────────────────────────────────────────┘

关键发现

  • 视频MAE需要**极高的掩码率(75-90%)**才能有效学习
  • 高掩码率强迫模型学习有用的表示
  • 极高掩码率使得自编码器变成了”预测”任务

掩码策略设计

class MaskingStrategy:
    """
    掩码策略
    """
    @staticmethod
    def random_masking(video, mask_ratio=0.75):
        """随机掩码"""
        return random_mask(video, mask_ratio)
    
    @staticmethod
    def tube_masking(video, tube_size=2):
        """管状掩码: 沿时间维度掩码"""
        # 对整个时空管掩码
        ...
    
    @staticmethod
    def checkerboard_masking(video):
        """棋盘格掩码"""
        # 空间棋盘格 + 时间维度
        ...
    
    @staticmethod
    def guided_masking(video, attention_scores):
        """引导掩码: 基于注意力分数选择掩码位置"""
        # 掩码低注意力区域可能更有效
        ...

时间一致性建模

时间顺序验证

**时间顺序验证(Temporal Order Verification)**是一种经典的视频SSL方法:

class TemporalOrderVerification(nn.Module):
    """
    时间顺序验证: 判断帧序列是否正确排序
    """
    def __init__(self, encoder):
        super().__init__()
        self.encoder = encoder
        self.classifier = nn.Linear(embed_dim, 2)  # 正/负样本
    
    def forward(self, frames):
        """
        frames: 3或5帧的片段
        """
        # 编码每帧
        features = [self.encoder(f) for f in frames]
        
        # 聚合特征
        aggregated = torch.stack(features).mean(dim=0)
        
        # 分类
        return self.classifier(aggregated)
    
    def generate_pairs(self, video):
        """生成正负样本对"""
        T = len(video)
        
        # 正样本: 正确顺序
        positive = video[0], video[2], video[4]  # 假设T≥5
        
        # 负样本: 随机打乱
        shuffled = video[torch.randperm(T)][:5]
        
        return positive, shuffled

帧顺序预测

**帧顺序预测(Frame Order Prediction)**扩展了时间顺序验证:

class FrameOrderPrediction(nn.Module):
    """
    帧顺序预测: 预测帧的正确顺序
    """
    def __init__(self, encoder, num_frames=3):
        super().__init__()
        self.encoder = encoder
        self.num_frames = num_frames
        
        # 预测每个位置应该放哪个时间步
        self.position_predictor = nn.Linear(embed_dim, num_frames)
    
    def forward(self, shuffled_frames):
        """
        shuffled_frames: 打乱顺序的帧
        """
        # 编码
        features = [self.encoder(f) for f in shuffled_frames]
        
        # 预测原始位置
        predictions = [self.position_predictor(f) for f in features]
        
        # 计算排序损失
        ...

慢速特征学习

**慢速特征分析(SFA)**强迫模型学习随时间缓慢变化的特征:

class SlowFeatureLoss(nn.Module):
    """
    慢速特征损失: 最小化相邻帧特征的变化
    """
    def __init__(self, alpha=1.0):
        super().__init__()
        self.alpha = alpha
    
    def forward(self, features):
        """
        features: [B, T, D] - 每帧的特征
        """
        # 计算相邻帧特征的差异
        diff = features[:, 1:] - features[:, :-1]
        
        # 最小化差异 (慢速目标)
        slow_loss = (diff ** 2).mean()
        
        return self.alpha * slow_loss

与图像预训练的结合

联合预训练策略

视频和图像的联合预训练可以利用大规模图像数据:

class JointVideoImagePretraining(nn.Module):
    """
    联合视频-图像预训练
    """
    def __init__(self, backbone):
        super().__init__()
        self.backbone = backbone
        
        # 视频特定的头
        self.video_head = VideoSSLHead()
        
        # 图像特定的头
        self.image_head = ImageSSLHead()
    
    def forward_video(self, video):
        features = self.backbone(video)
        return self.video_head(features)
    
    def forward_image(self, image):
        features = self.backbone(image)
        return self.image_head(features)

预训练-微调策略

策略描述优点缺点
从头训练仅使用视频数据专门针对视频需要大量视频数据
ImageNet初始化使用图像预训练权重利用大规模图像数据可能不捕捉时间信息
联合预训练视频+图像联合训练结合两者优势实现复杂
渐进式微调图像→视频逐步迁移稳定迁移需要仔细设计

迁移学习注意事项

def adapt_image_pretrained_for_video(pretrained_state_dict):
    """
    将图像预训练权重适配到视频模型
    """
    adapted = {}
    
    for key, value in pretrained_state_dict.items():
        if 'patch_embed' in key:
            # 扩展2D patch embedding到3D
            # 将 [C, H*W, D] → [C, T*H*W, D]
            # 或复制2D权重到时间维度
            adapted[key] = expand_2d_to_3d(value)
        elif 'pos_embed' in key:
            # 扩展位置编码到3D
            adapted[key] = expand_2d_to_3d_pos_embed(value)
        else:
            # 直接使用
            adapted[key] = value
    
    return adapted

下游任务微调

线性探测 vs 全微调

视频SSL预训练后,通常有两种微调策略:

策略描述适用场景
线性探测仅训练线性分类头预训练表示质量高
全微调训练所有参数数据充足、任务差异大
Partial微调训练部分层平衡方案

微调技巧

class VideoFineTuner:
    """
    视频微调策略
    """
    def __init__(self, model):
        self.model = model
    
    def fine_tune(self, pretrained_path, train_data, method='full'):
        # 加载预训练权重
        state_dict = load_pretrained(pretrained_path)
        self.model.load_state_dict(state_dict)
        
        if method == 'linear':
            # 冻结backbone
            for param in self.model.backbone.parameters():
                param.requires_grad = False
            
            # 只训练分类头
            self.optimizer = torch.optim.Adam(
                self.model.head.parameters(), lr=1e-3
            )
        
        elif method == 'partial':
            # 冻结前几层
            freeze_layers(self.model.backbone, num_layers=6)
            
            self.optimizer = torch.optim.AdamW(
                self.model.parameters(), lr=1e-4, weight_decay=0.05
            )
        
        elif method == 'full':
            # 全微调,使用较小学习率
            self.optimizer = torch.optim.AdamW(
                self.model.parameters(), lr=1e-5, weight_decay=0.01
            )
        
        # 训练循环
        ...

方法对比

在Kinetics-100上的线性探测结果

方法Top-1预训练数据
从头训练58.4%-
ImageNet监督69.3%ImageNet
MoCo v267.7%K400
SimCLR69.1%K400
CoCLR72.1%K400
VideoMAE74.8%K400

关键发现

  1. 高掩码率对视频MAE至关重要:75-90%的掩码率效果最好
  2. 光流信息很有价值:CoCLR利用光流显著提升性能
  3. 时间增强很重要:时间维度的数据增强对视频SSL更关键
  4. 联合预训练有效:结合图像和视频数据可以进一步提升

总结

视频自监督学习利用视频的时序特性学习有效表示,主要方法包括:

  1. 视频对比学习:如CoCLR,通过跨模态对比学习视觉和运动表示
  2. 视频预测与重建:如VideoGPT,通过预测未来或重建缺失帧学习表示
  3. 掩码视频建模:如VideoMAE,通过重建被掩码的时空patch学习表示
  4. 时间一致性建模:如帧顺序预测,通过学习时间动态学习表示

这些方法可以结合使用,如结合对比学习和掩码建模,以获得更丰富的表示。


参考文献

Footnotes

  1. Han T, Xie W, Zisserman A. Self-supervised co-training for video representation learning[J]. Advances in Neural Information Processing Systems, 2020, 33: 12596-12608.

  2. Yan W, Zhang Y, Szwarcman P, et al. Videogpt: Video generation using vq-vae and transformers[J]. arXiv preprint arXiv:2104.10157, 2021.

  3. Tong Z, Song Y, Wang J, et al. Videomae: Masked autoencoders are data-efficient learners for self-supervised video pre-training[J]. Advances in neural information processing systems, 2022, 35: 10078-10093.