029、层级交互的艺术:HAN层级注意力网络的创新点解析与训练技巧
029、层级交互的艺术:HAN层级注意力网络的创新点解析与训练技巧
去年夏天,我接手了一个老照片修复项目。客户拿来的是一批上世纪80年代的全家福,扫描分辨率只有72dpi,人脸区域大概就30×30像素。我试了SRCNN、EDSR、RCAN,效果都不理想——要么纹理糊成一团,要么出现诡异的伪影。直到我偶然翻到一篇CVPR 2020的论文,HAN(Hierarchical Attention Network),才真正找到突破口。
当时我盯着HAN的结构图看了整整一个下午,脑子里反复转着一个念头:为什么之前的注意力机制都只关注“局部”或“全局”的单一层级,而HAN偏偏要把它们串起来?后来我在自己的数据集上跑通HAN后,才恍然大悟——这玩意儿解决的是超分任务里一个极其隐蔽但致命的问题:特征之间的“层级割裂”。
从“注意力孤岛”到“层级对话”
先说说我踩过的坑。早期用RCAN时,我发现它的通道注意力(Channel Attention)确实能自适应地调整不同通道的重要性,但有个毛病:每个残差组(Residual Group)内部的注意力是独立的。打个比方,就像一群人在开会,每个人只盯着自己手里的笔记本,完全不管隔壁桌在讨论什么。这导致浅层特征和深层特征之间缺乏交互,尤其是当图像包含复杂的纹理和边缘时,模型容易“顾此失彼”。
HAN的聪明之处在于,它引入了三种注意力机制,并且让它们形成一种“层级对话”的关系:通道注意力负责筛选每个残差组内部的重要特征,空间注意力关注像素级别的空间相关性,而最核心的层级注意力(Layer-wise Attention)则像是一个“调度员”,把不同残差组的输出动态加权融合。
别小看这个“调度员”。传统做法是把所有残差组的输出直接拼接或简单相加,但HAN的做法是:先对每个残差组的输出做全局平均池化,得到一个描述向量,然后通过一个轻量级的全连接网络学习出每个组的权重。这相当于让模型自己判断“哪一层的特征对当前重建更重要”。我在调试时发现,对于纹理密集的区域(比如头发丝),模型往往会给中间层更高的权重;而对于平滑区域(比如天空),浅层和深层权重更均衡。
代码实现里的“暗坑”
理论说完了,直接上代码。这里我用PyTorch复现HAN的核心模块,重点标注那些容易翻车的地方。
importtorchimporttorch.nnasnnimporttorch.nn.functionalasFclassLayerAttention(nn.Module):def__init__(self,n_feats,n_resgroups,reduction=16):super(LayerAttention,self).__init__()# 注意:这里的n_resgroups是残差组的数量,不是通道数# 我一开始写成了n_feats // reduction,结果维度对不上,debug了一整天self.body=nn.Sequential(nn.Conv2d(n_resgroups*n_feats,n_feats//reduction,1,padding=0,bias=True),nn.ReLU(inplace=True),nn.Conv2d(n_feats//reduction,n_resgroups,1,padding=0,bias=True),nn.Sigmoid())defforward(self,x):# x的shape: [B, n_resgroups, C, H, W]# 别这样写:直接对x做全局平均池化,会丢失组间信息# 正确做法:先reshape成[B, n_resgroups*C, H, W]b,g,c,h,w=x.shape x_reshaped=x.view(b,g*c,h,w)# 全局平均池化得到描述向量gap=F.adaptive_avg_pool2d(x_reshaped,1)# [B, g*c, 1, 1]# 通过全连接层得到每个组的权重weights=self.body(gap)# [B, g, 1, 1]# 权重广播到每个组的特征上weights=weights.view(b,g,1,1,1)return(x*weights).sum(dim=1)# 加权求和,输出[B, C, H, W]这里有个细节:为什么要把所有组的特征先拼接再池化?因为如果单独对每个组做池化,会丢失组间的相对信息。HAN的论文里提到,这种“全局描述”能让模型感知到不同层级特征的“能量分布”。
训练技巧:别让层级注意力“躺平”
我在训练HAN时遇到一个典型问题:层级注意力模块的权重很快收敛到均匀分布,也就是每个组的权重都差不多,等于没起作用。这通常是因为学习率设置不当,或者初始化策略有问题。
技巧一:给层级注意力模块单独设置学习率。我习惯把主干网络的学习率设为1e-4,而层级注意力模块设为5e-4,让它“跑得快一点”。别担心过拟合,这个模块参数极少(就两个卷积层),稍微激进一点没关系。
技巧二:在损失函数里加入权重熵正则项。如果层级注意力的权重分布太均匀,说明模型偷懒了。可以在L1损失基础上加一个惩罚项:loss += 0.001 * (-weights * torch.log(weights + 1e-8)).sum()。这能迫使权重分布更尖锐,让模型真正学会“选择”。
技巧三:数据增强要“温柔”。超分任务里,随机裁剪、翻转、旋转是常规操作,但HAN对几何变换比较敏感。我试过用RandomResizedCrop,结果层级注意力的权重分布变得极其不稳定。后来改成固定尺寸的随机裁剪(比如96×96),效果稳定很多。原因可能是:HAN的层级注意力依赖于全局统计信息,如果裁剪区域变化太大,全局池化的结果会剧烈波动。
实战效果:老照片修复的“救星”
回到开头那个项目。用HAN处理30×30的人脸区域,效果让我吃了一惊——虽然不可能凭空恢复出皱纹或毛孔,但至少把五官轮廓从“马赛克”变成了“模糊但可辨认”。对比RCAN,HAN在边缘处少了很多振铃效应,尤其是在头发和背景的交界处。
我后来分析了一下原因:RCAN的通道注意力只关注“哪些通道重要”,而HAN的层级注意力额外关注“哪些层级的特征重要”。对于人脸这种结构性强、纹理分布不均匀的图像,浅层特征(边缘、梯度)和深层特征(语义、轮廓)需要动态平衡。HAN的“调度员”恰好做到了这一点。
个人经验:什么时候该用HAN?
如果你做的是通用超分(比如DIV2K、Set5这些标准数据集),HAN的提升可能只有0.1-0.2dB,不值得折腾。但如果你面对的是纹理复杂、结构多变的场景(比如人脸、遥感图像、医学影像),HAN的层级交互机制会带来肉眼可见的改善。
另外,HAN的参数量比RCAN略大(多了一个层级注意力模块),但推理速度几乎没影响,因为那个模块只做了一次全局池化和两个1×1卷积。所以别担心性能开销。
最后说一句:别迷信论文里的默认超参数。我在训练时把残差组的数量从10减少到6,层级注意力的效果反而更好——因为组数太多时,权重分布容易“稀释”。调参这件事,还是得自己动手试。