课程进度 课程大纲 已发布 34/34 课
第 0 部分——先拿到地图
第 1 部分——为什么简单目标会学会看视频
第 2 部分——把一段视频送进单编码器
第 3 部分——会读实验,才算读懂论文
第 4 部分——从官方仓库到自己的实验
第 5 部分——把表征接回世界模型路线
附录——随用随查的技术背包
给每个张量贴一张货运单
- 视频箱
B,T,C,H,W - 多扇窗全局一张,局部 V 张
- patch每帧切成 16×16
- 稀疏 token训练只留 5%
- 摘要卡
V+1,B,K
一只行李箱先按“旅客—时间—颜色—高度—宽度”摆放,进安检前却要改成“旅客—颜色—时间—高度—宽度”。若标签换了、箱子却没有换位置,机器仍会运行,只是把 16 帧误当成 16 个颜色通道。
这类错误最麻烦的地方在于,它未必立刻报错。“5D tensor”还不是一份够用的说明;给每一维写上名字,再在第一批数据上断言形状、类型和值域,才能知道箱子真的换过位置。
先办好符号护照
| 符号 | 含义 | 论文/公开配方中的常见值 |
|---|---|---|
B | 每个设备上的 batch 大小 | 仓库默认 96;全局有效 batch 另算 |
V | 每段视频的局部视图数 | 论文多数受控比较 4;仓库默认 10 |
T | 输入帧数 | 16 |
C | 颜色通道 | 3 |
Hg,Wg | 全局视图高、宽 | 224,224 |
Hl,Wl | 局部视图高、宽 | 96,96 |
P | 空间 patch 边长 | 16 |
τ | tubelet 的时间长度 | 默认 1,即逐帧 token 化 |
D | ViT 隐藏宽度 | ViT-B 为 768;ViT-L checkpoint 为 1024 |
K | projector 输出维数 | 256 |
ρ | token 丢弃率 | 0.95 |
M | SIGReg 随机方向数 | 1024 |
Q | SIGReg 积分节点数 | 17 |
“论文常用值”和“官方仓库默认值”不是同一列配置。尤其 V=4 与 V=10 都可能正确,必须同时说明实验协议。
第一段传送带:加载器到多视图
单个 episode 中抽出的原始片段通常是:
[T,C,H_raw,W_raw]
多视图变换对同一组 T 帧做空间裁剪。组成 batch 后,公开加载器给出:
| 名称 | 形状 | 说明 |
|---|---|---|
global_frame | [B,T,C,224,224] | 一张近全局、无局部光度增强的视图 |
local_frames | [B,V,T,C,96,96] | V 张独立空间裁剪并做光度增强的视图 |
进入编码器前,main.py 用 einops.rearrange 改变轴顺序:
global_video = rearrange(global_frame, "b t c h w -> b c t h w")
local_video = rearrange(local_frames, "b v t c h w -> (b v) c t h w")
于是全局输入为 [B,C,T,224,224],局部输入为 [B·V,C,T,96,96]。把视图并入 batch 维能让同一编码器一次处理所有局部窗口;输出后再拆回 B,V,D。
第二段:切 patch 之前先数格子
τ=1 时,每个 token 只跨一帧;每个空间 patch 是 16×16:
全局 patch 数 = 16 × (224/16) × (224/16) = 16 × 14 × 14 = 3136
局部 patch 数 = 16 × ( 96/16) × ( 96/16) = 16 × 6 × 6 = 576
patch embedding 后、加入 [CLS] 前:
| 分支 | 训练前的 patch token | 形状 |
|---|---|---|
| 全局 | 3136 | [B,3136,D] |
| 局部 | 576 | [B·V,576,D] |
若 τ=2,时间槽减半为 8;这会改变 token 数和每个 token 覆盖的时间范围。LeVJEPA 默认选择 τ=1,而 V-JEPA/VideoMAE 的 tubelet=2 属于另一套配方,替换后应当明确标成模型变化。
第三段:95% 丢弃把序列变多短
官方代码用:
keep_len = round(N_patches × (1 - ρ))
每个样本独立产生随机保留集合:
| 分支 | 原 patch | 保留 patch | 加 [CLS] 后的序列长度 |
|---|---|---|---|
| 全局 | 3136 | round(156.8)=157 | 158 |
| 局部 | 576 | round(28.8)=29 | 30 |
这只是训练形状。评估和推理时不丢 token,所以公开 ViT-L 权重在 16×224×224 输入上返回 3136+1=3137 个 token。训练时的 158 与 checkpoint 输出的 3137 一旦混用,后续 dense feature 代码就会错位。
第四段:编码器、[CLS] 与 projector
训练时:
global_tokens : [B, 158,D] # 默认丢弃率下
local_tokens : [B·V, 30,D]
global_cls : [B, 1,D]
local_cls : [B, V,D]
concat cls : [B, V+1,D]
projected z : [B, V+1,K]
代码用 embeddings[:, :1] 保留全局嵌入 [B,1,K];广播后与全部 [B,V+1,K] 比较。全局与自己相减的那一项恒为零,但仍被包含在平均值中,这与论文公式和官方代码一致。
SIGReg 前又把轴换成:
[B,V+1,K] -> [V+1,B,K]
这样它会为每个视图位置单独检查 B 个样本组成的分布,而不是把同一视频的多个相关视图当成额外独立样本。
第五段:SIGReg 内部还要换一次轴
设输入为 [S,B,K],其中 S=V+1:
| 步骤 | 形状 | 含义 |
|---|---|---|
随机单位方向 A | [K,M] | M=1024 支手电筒 |
投影 z @ A | [S,B,M] | 每个样本在每个方向上的影子 |
| 乘积分节点 | [S,B,M,Q] | Q=17 个频率 |
| cos/sin batch 均值 | [2,S,M,Q] | 经验特征函数实部与虚部 |
| 与高斯指纹的误差 | [S,M,Q] | 每个视图、方向、节点的差 |
| 最终 loss | [] | 对视图、方向、节点加权平均 |
分布式 all-reduce 聚合的是 [2,S,M,Q] 的经验特征函数,不是把所有 [B,K] 嵌入直接传来传去。这解释了论文所说的通信量基本不随 batch 与嵌入维数增长。
到站:发布权重的推理接口
官方 Hugging Face 模型期望:
pixel_values: [B,C,T,H,W]
对 [1,3,16,224,224],ViT-L/16 的公开接口报告:
last_hidden_state: [1,3137,1024] # CLS + 16×14×14 patch
pooler_output: [1,1024] # CLS
单张图像若按模型卡重复为 16 帧,输出仍是视频 token 序列;这不代表模型看到了运动。重复帧只是一种让视频编码器接收静态图像的适配方式。
把五条断言留在测试里
assert global_frame.ndim == 5 and global_frame.shape[1:3] == (16, 3)
assert local_frames.ndim == 6 and local_frames.shape[2:4] == (16, 3)
assert global_frame.shape[-2:] == (224, 224)
assert local_frames.shape[-2:] == (96, 96)
assert torch.isfinite(loss).all()
还应打印归一化前后的 dtype/min/max/mean/std。形状正确但把 uint8 0..255 直接送入期望归一化浮点输入,同样会产生“能运行的错误”。
货运单的原始凭据
LeVJEPA arXiv v1 方法与附录给出符号和架构;固定快照中的 main.py、module.py 与 data/loader.py定义可执行形状;固定版本的模型卡定义 checkpoint 接口。
三张不能贴反的标签
- 加载器是
[B,T,C,H,W],编码器是[B,C,T,H,W]。 - 95% 丢弃只发生在训练;推理会返回全部 3137 个 token。
- SIGReg 的 batch 维是不同视频样本,不是同一视频的视图数。
查一遍货运单
- 为什么局部视图进入编码器时是
[B·V,C,T,96,96]? - 全局训练序列为何是 158,而公开 checkpoint 输出为何是 3137?
- 把
[B,V+1,K]直接当[B(V+1),K]做 SIGReg 有什么概念风险?
箱子应当怎样摆
- 视图维临时并入 batch,所有局部窗口由同一编码器并行处理;之后再拆回。
- 前者随机保留 157 个 patch 再加 CLS;后者处于评估模式,不丢 3136 个 patch。
- 它会把同一视频的强相关视图当成独立人群成员,改变所估计的分布与有效样本量。