083、VSRT:视频超分中的Transformer架构——时空注意力机制详解
上个月调一个视频超分模型,跑了两天两夜,loss曲线漂亮得像教科书上的示范图,结果一上测试集,相邻帧之间的闪烁能把人眼睛晃瞎。我盯着输出视频看了十分钟,差点以为自己得了飞蚊症。后来排查到问题根源——模型压根没学会跨帧建模,每一帧都在“自说自话”。这就是典型的时空注意力没做好的下场。
今天聊VSRT,这个把Transformer塞进视频超分的经典工作。不扯那些花里胡哨的公式推导,咱们直接从代码和调参的视角,把时空注意力这块硬骨头啃下来。
为什么CNN在视频超分上总差一口气
先说说为什么非得用Transformer。EDVR、BasicVSR这些CNN架构,靠光流对齐+可变形卷积,在视频超分上已经打下一片江山。但有个致命伤——感受野受限。你叠再多的3D卷积,相邻帧之间的长程依赖还是抓不住。比如一个物体被遮挡了两三帧又出现,CNN基本就懵了,要么产生鬼影,要么直接丢失纹理。
Transformer的self-attention天然适合干这事。它能在一帧内部捕捉全局依赖(空间注意力),也能跨帧建立联系(时间注意力)。VSRT的核心贡献,就是把这两种注意力有机地拧在一起,而不是简单粗暴地拼接。
VSRT的时空注意力到底怎么设计的
VSRT的架构可以拆成三块:空间注意力模块、时间注意力模块、以及一个巧妙的交互机制。别被论文里的“交错式”这种词唬住,说白了就是让空间和时间信息互相“串门”。
空间注意力:不是简单的ViT
很多初学者直接拿ViT的patch embedding往视频帧上套,结果发现显存直接爆炸。VSRT的做法更聪明——它把每一帧切分成非重叠的patch,但注意,这里有个关键设计:不同帧的patch数量保持一致,但位置编码是独立的。
# 别这样写:直接把所有帧拼成一个大序列# frames = rearrange(frames, 'b t c h w -> b (t h w) c') # 显存爆炸警告# 正确的做法:逐帧处理,保持帧间独立性defspatial_attention(self,x):# x shape: [B, T, C, H, W]B,T,C,H,W=x.shape# 每帧独立切patchpatches=x.unfold(3,self.patch_size,self.patch_size).unfold(4,self.patch_size,self.patch_size)patches=patches.contiguous().view(B,T,C,-1,self.patch_size,self.patch_size)# 这里踩过坑:unfold之后一定要contiguous,不然后面reshape会出玄学错误patches=patches.permute(0,1,3,2,4,5).contiguous()patches=patches.view(B,T,-1,C*self.patch_size*self.patch_size)# 对每帧独立做self-attentionfortinrange(T):patches[:,t]=self.self_attn(patches[:,t])returnpatches空间注意力的核心是窗口限制。VSRT没有做全局self-attention,而是用了局部窗口,窗口大小一般是7x7或8x8。为什么?因为视频帧的纹理细节通常是局部的,全局注意力反而会引入噪声。这个设计后来被很多工作借鉴,包括SwinIR。
时间注意力:跨帧对齐的优雅解法
时间注意力是VSRT最出彩的地方。它没有用光流做显式对齐,而是让模型自己学怎么“看”相邻帧。
具体做法是:对每个空间位置,在时间维度上做self-attention。比如第t帧的某个patch,它会去“关注”第t-1帧、第t-2帧对应位置的patch,以及第t+1帧、第t+2帧的patch。这个范围(时间窗口)是个超参数,论文里设的是5帧(前后各2帧)。
deftemporal_attention(self,x,temporal_window=5):# x shape: [B, T, N, D] N是patch数量,D是特征维度B,T,N,D=x.shape half_window=temporal_window//2# 这里有个坑:边界帧的处理# 别这样写:直接padding零# x_padded = F.pad(x, (0,0,0,0, half_window, half_window)) # 会引入边界伪影# 正确的做法:循环padding,或者用相邻帧复制x_padded=torch.cat([x[:,:half_window+1].flip(1),x,x[:,-half_window-1:].flip(1)],dim=1)output=[]fortinrange(half_window,half_window+T):# 提取时间窗口内的特征window=x_padded[:,t-half_window:t+half_window+1]# [B, temporal_window, N, D]# 在时间维度上做attentionattn=torch.einsum('btn d, b t n d -> b t n n',window,window)/math.sqrt(D)attn=F.softmax(attn,dim=-1)out=torch.einsum('b t n m, b m n d -> b t n d',attn,window)output.append(out[:,half_window])# 只取中心帧的输出returntorch.stack(output,dim=1)这个设计的精妙之处在于:空间位置是绑定的。第t帧的patch只关注其他帧相同位置的patch,这相当于做了一个隐式的光流对齐。模型通过注意力权重的大小,自动学会哪些帧的对应位置信息有用,哪些是遮挡或噪声。
时空交互:关键中的关键
如果只是空间注意力+时间注意力串行,效果其实一般。VSRT的杀手锏是交叉注意力——让空间和时间信息在同一个特征空间里交互。
具体实现上,VSRT用了两个并行的注意力分支,然后通过一个门控机制融合:
defspatio_temporal_fusion(self,spatial_feat,temporal_feat):# spatial_feat: [B, T, N, D] 空间注意力输出# temporal_feat: [B, T, N, D] 时间注意力输出# 门控机制:让模型自己决定每个位置该信任哪个分支gate=torch.sigmoid(self.gate_conv(torch.cat([spatial_feat,temporal_feat],dim=-1)))# 这里踩过坑:gate的初始化很重要,初始值偏0.5效果最好# 如果初始值偏0或1,模型容易陷入局部最优fused=gate*spatial_feat+(1-gate)*temporal_feat# 再加一层cross-attention,让两个分支的信息充分混合fused=self.cross_attention(fused,fused,fused)returnfused这个门控机制解决了视频超分的一个经典问题:静态区域和动态区域需要不同的处理策略。静态区域(比如背景)空间注意力就够了,动态区域(比如运动物体)需要更多时间信息。门控让模型自己学会区分。
训练中的那些坑
VSRT的训练比普通超分模型要敏感得多。我踩过的坑包括:
学习率设置:Transformer模块的学习率要比CNN模块小一个数量级。我试过统一用1e-4,结果空间注意力模块直接发散。后来改成CNN部分1e-4,Transformer部分1e-5,才稳定下来。
梯度裁剪:这个一定要加。视频序列长,梯度容易爆炸。我设的max_norm=1.0,效果不错。别设太小,0.1的话模型基本学不动。
时间窗口的选择:不是越大越好。我试过7帧窗口,显存直接翻倍,效果反而下降了。原因很简单:相隔太远的帧,对应位置的内容可能已经完全变了,强行让它们做attention只会引入噪声。5帧是个比较稳妥的选择。
位置编码:VSRT用了可学习的位置编码,但要注意,空间位置编码和时间位置编码要分开。我一开始图省事,把时空位置编码合并成一个,结果模型分不清“第1帧左上角”和“第5帧左上角”的区别。
实战效果与局限性
VSRT在REDS4和Vid4上的PSNR比EDVR高了0.3-0.5dB,这个提升在视频超分领域已经相当可观。更关键的是,它的输出视频几乎看不到闪烁,时间一致性比CNN方法好太多。
但VSRT也有明显的短板:
计算量太大。一个VSRT-Large模型,处理30帧的720p视频,需要24GB显存。这在实际应用中基本不可行。后来的一些工作如BasicVSR++,通过光流引导的注意力,把计算量降了下来。
对运动剧烈的场景处理不好。当物体运动速度超过每帧20个像素,时间注意力的对齐效果就会变差。因为对应位置的patch可能已经完全不相关了。这个问题直到后来用光流做显式对齐才得到缓解。
长视频的累积误差。VSRT是逐段处理的(比如每5帧一组),组与组之间没有信息传递。处理长视频时,不同组之间的亮度、颜色可能会有跳变。这个问题的解决方案是加一个时序平滑损失,或者用循环结构。
个人经验总结
如果你现在要做一个视频超分项目,我的建议是:
别一上来就上VSRT。先跑通一个简单的CNN baseline,比如EDVR,确保数据流程、训练策略都没问题。VSRT的训练太敏感,debug成本极高。
如果非要用Transformer,先从轻量级开始。VSRT-Tiny只有4个Transformer block,参数量不到10M,效果已经比大多数CNN方法好。没必要一开始就上Large版本。
注意数据增强。视频超分的数据增强比图像超分更讲究。随机裁剪、水平翻转这些常规操作没问题,但别用颜色抖动,因为视频帧之间的颜色一致性很重要。我试过加颜色抖动,结果模型学会了忽略颜色信息,输出全是灰蒙蒙的。
监控时间一致性。除了PSNR和SSIM,一定要加一个时间一致性指标,比如temporal flickering metric。很多模型PSNR很高,但视频看起来就是不舒服,就是因为时间一致性差。
VSRT这篇工作虽然已经过去几年,但它提出的时空注意力设计思路,至今仍在影响视频超分领域。后来的很多工作,比如Video Swin Transformer、TimeLens,都能看到VSRT的影子。理解它的核心思想,对你理解整个视频超分领域的发展脉络,会有很大帮助。