引言
视频自监督学习旨在在没有人工标注的情况下学习有效的视频表示。与图像自监督学习相比,视频自监督学习可以利用额外的时间维度信息,学习到丰富的时序动态和运动模式表示。
本文系统介绍视频自监督学习的核心方法,包括视频对比学习、视频预测与重建、掩码视频建模、时间一致性建模等方向,以及与图像预训练的结合策略。
视频自监督学习概述
与图像SSL的区别
视频自监督学习相比图像自监督学习具有独特的优势和挑战:
| 方面 | 图像SSL | 视频SSL |
|---|---|---|
| 数据属性 | 静态2D | 动态3D(时间+空间) |
| 可用信息 | 空间上下文、纹理、颜色 | 运动、光流、时间顺序 |
| ** pretext task** | 旋转、拼图、对比 | 时间顺序、跟踪、预测 |
| 挑战 | 语义不变性 | 时序一致性 |
| 应用 | 分类、检测 | 动作识别、跟踪、分割 |
额外的时间维度信息
视频包含丰富的时序信息:
- 运动信息:物体如何在时间中移动
- 时间顺序:事件发生的先后关系
- 周期性:重复模式(如走路、跑步)
- 因果关系:什么导致什么发生
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 lossCoCLR
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的关键设计:
- 双流架构:视觉流 + 光流流
- 跨模态对比:视觉表示与光流表示互相预测
- 在线对比:不需要额外的队列或动量更新
时间增强的重要性
视频对比学习中的时间增强策略对性能至关重要:
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_framesVideoGPT
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 v2 | 67.7% | K400 |
| SimCLR | 69.1% | K400 |
| CoCLR | 72.1% | K400 |
| VideoMAE | 74.8% | K400 |
关键发现
- 高掩码率对视频MAE至关重要:75-90%的掩码率效果最好
- 光流信息很有价值:CoCLR利用光流显著提升性能
- 时间增强很重要:时间维度的数据增强对视频SSL更关键
- 联合预训练有效:结合图像和视频数据可以进一步提升
总结
视频自监督学习利用视频的时序特性学习有效表示,主要方法包括:
- 视频对比学习:如CoCLR,通过跨模态对比学习视觉和运动表示
- 视频预测与重建:如VideoGPT,通过预测未来或重建缺失帧学习表示
- 掩码视频建模:如VideoMAE,通过重建被掩码的时空patch学习表示
- 时间一致性建模:如帧顺序预测,通过学习时间动态学习表示
这些方法可以结合使用,如结合对比学习和掩码建模,以获得更丰富的表示。
参考文献
Footnotes
-
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. ↩
-
Yan W, Zhang Y, Szwarcman P, et al. Videogpt: Video generation using vq-vae and transformers[J]. arXiv preprint arXiv:2104.10157, 2021. ↩
-
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. ↩