027、注意力机制的革命:RCAN残差通道注意力网络的原理与超分实战
027、注意力机制的革命:RCAN残差通道注意力网络的原理与超分实战
去年有个项目让我印象特别深——客户要求把监控视频中的人脸从64×64放大到512×512,还要保留足够的细节用于身份识别。我一开始用SRResNet跑,结果出来的脸跟打了马赛克似的,眼睛糊成一团。后来换成EDSR,效果好了点,但高频纹理还是出不来。直到我翻到RCAN那篇论文,才意识到问题出在哪:我们一直在空间维度上做文章,却忽略了通道维度上信息的重要性。
为什么RCAN能吊打之前的网络
先说说RCAN的核心思想。传统的超分网络,比如SRResNet、EDSR,它们用的残差块都是“平等对待”每个通道的。但实际上一张图像的不同通道携带的信息量完全不同——有的通道包含丰富的边缘纹理,有的通道可能全是噪声。RCAN的聪明之处在于,它让网络学会“关注”那些重要的通道,抑制不重要的通道。
这个机制叫通道注意力(Channel Attention)。简单来说,就是给每个通道学一个权重,重要的通道权重高,不重要的权重低。但RCAN不是简单加个SE模块就完事了,它做了两件更狠的事:一是把通道注意力和残差结构深度耦合,形成了残差通道注意力块(RCAB);二是用残差中的残差(RIR)结构,让网络能堆到400多层还不退化。
残差通道注意力块(RCAB)的实现细节
RCAB的结构其实不复杂,但有几个坑必须说清楚。先看核心代码:
classRCAB(nn.Module):def__init__(self,n_feats,reduction=16):super(RCAB,self).__init__()# 这里reduction=16是经验值,别乱改,后面会解释self.body=nn.Sequential(nn.Conv2d(n_feats,n_feats,3,padding=1),nn.ReLU(True),nn.Conv2d(n_feats,n_feats,3,padding=1))# 通道注意力部分self.ca=nn.Sequential(nn.AdaptiveAvgPool2d(1),# 全局平均池化,把空间维度压缩到1x1nn.Conv2d(n_feats,n_feats//reduction,1),# 降维,这里踩过坑:reduction不能太小nn.ReLU(True),nn.Conv2d(n_feats//reduction,n_feats,1),# 升维回原通道数nn.Sigmoid()# 输出0-1之间的权重)defforward(self,x):residual=x out=self.body(x)# 通道注意力权重weight=self.ca(out)# 别这样写:out = out * weight.expand_as(out)# 直接用广播机制,PyTorch会自动处理维度out=out*weightreturnout+residual# 残差连接这里有个细节很多人会忽略:全局平均池化之后,特征图的尺寸变成了1×1,这时候用1×1卷积做通道间的信息交互。reduction=16的意思是先把通道数压缩到原来的1/16,再升回来。为什么是16?论文里做过消融实验,8、16、32都试过,16效果最好。我试过reduction=8,参数量上去了但效果没提升,反而容易过拟合。
残差中的残差(RIR)结构
RCAN能堆到400多层,靠的就是RIR结构。它把多个RCAB组成一个残差组(RG),然后再把多个RG串起来,外面再套一层长残差连接。
classRCAGroup(nn.Module):def__init__(self,n_feats,n_rcab,reduction):super(RCAGroup,self).__init__()# n_rcab表示这个组里有多少个RCAB块body=[]for_inrange(n_rcab):body.append(RCAB(n_feats,reduction))self.body=nn.Sequential(*body)# 组内残差连接,这里用1x1卷积做通道对齐self.conv=nn.Conv2d(n_feats,n_feats,1)defforward(self,x):residual=x out=self.body(x)out=self.conv(out)returnout+residual注意看,每个RG内部有一个短残差,多个RG之间还有长残差。这种设计让梯度可以沿着多条路径回传,解决了深层网络的梯度消失问题。我试过不加长残差,20个RG就开始不收敛了。
训练RCAN的那些坑
第一个坑是学习率。RCAN的参数量比EDSR大不少,我用Adam优化器,初始学习率设1e-4,结果loss震荡得厉害。后来改成1e-4跑50个epoch,然后每50个epoch衰减0.5,才稳定下来。
第二个坑是batch size。RCAN的显存消耗很大,我的RTX 3090只能跑batch size=16,输入patch size=48×48。如果显存不够,可以试试梯度累积,但注意BN层的统计量会受影响。
第三个坑是数据增强。我一开始只做了随机翻转和旋转,效果一般。后来加了随机裁剪和颜色抖动,PSNR提升了0.3dB左右。但注意不要用太强的颜色抖动,超分任务对颜色一致性要求很高。
实战:用RCAN做4倍超分
完整的训练代码我就不贴了,说几个关键点。
数据预处理:用DIV2K数据集,把HR图像切成96×96的patch,LR图像用bicubic下采样到24×24。注意下采样要用PIL的Image.BICUBIC,别用OpenCV的INTER_CUBIC,两者结果有差异。
损失函数:我用L1损失,比L2损失收敛更快,而且PSNR更高。别问我为什么,实验就是这么说的。
评估指标:PSNR和SSIM都要看。PSNR反映像素级误差,SSIM反映结构相似性。有时候PSNR高但SSIM低,说明图像虽然像素误差小,但纹理细节丢失了。
个人经验性建议
别盲目追求层数。RCAN原论文用了400层,但实际应用中200层就够了。层数越多,训练越慢,而且边际效益递减。我试过800层,PSNR只提升了0.05dB,但训练时间翻了一倍。
通道注意力不是万能的。对于纹理丰富的图像(比如建筑、自然风景),RCAN效果很好。但对于人脸、文字这类结构化的图像,空间注意力可能更有效。我现在的做法是把通道注意力和空间注意力结合起来,效果更好。
注意reduction参数。这个参数控制通道注意力的压缩比,直接影响模型容量。对于小数据集(比如Set5、Set14),reduction=16就够了。对于大数据集(比如DIV2K、Flickr2K),可以试试reduction=8。
训练时监控每个RCAB的权重分布。如果大部分权重都集中在0.5附近,说明通道注意力没学到东西,可能是网络太浅或者数据量不够。
最后说个玄学:RCAN对输入图像的噪声很敏感。如果LR图像有噪声,先做去噪再超分,效果比端到端训练好。我试过在RCAN前面加个DnCNN,PSNR提升了0.8dB。
RCAN的出现确实推动了超分领域的发展,但它不是终点。现在大家都在做Transformer和注意力机制的融合,比如SwinIR、HAT等。但RCAN的思想——用通道注意力让网络学会关注重要信息——这个思路永远不会过时。