前言
论文题目:Efficient Image Super-Resolution Using Pixel Attention —— 基于像素注意的高效图像超分辨率
论文地址:Efficient Image Super-Resolution Using Pixel Attention
论文源码:https://github.com/zhaohengyuan1/PAN
1. 模型介绍
这项工作旨在设计一种用于图像超分辨率(SR)的轻量级卷积神经网络。在不考虑简单性的情况下,我们使用新提出的像素注意力方案构建了一个非常简洁有效的网络。像素注意力(Pixel Attention,PA)类似于公式中的通道注意力和空间注意力。不同之处在于,PA生成3D注意力图而不是1D注意力矢量或2D图。该注意力策略引入了较少的附加参数,但生成了更好的SR结果。在PA的基础上,我们分别为主分支和重建分支提出了两个构造块:第一个SC-PA块的结构与自校准卷积相同,但具有我们的PA层。由于其双分支架构和注意力方案,该模块块比常规的残差 / 密集块效率更高;第二个U-PA块结合了最近邻上采样、卷积和PA层。它以很少的参数成本提升了最终的重建质量。我们的最终模型PAN可以达到与轻量级网络SRResNet和CARN相似的性能,但是参数只有272K(SRResNet的17.92%和CARN的17.09%)。每个提出组件的有效性也通过消融研究得到验证。
2. 像素注意力(Pixel Attention)
像素注意力机制与通道 / 空间注意力机制的区别就是:
- 通道注意力生成1 × \times × 1 × \times × C维度的注意力权重
- 空间注意力生成H × \times × W × \times × 1维度的注意力权重
- 像素注意力生成H × \times × W × \times × C维度的注意力权重
像素注意力的网络结构也非常简单,就是1 × \times × 1卷积 + sigmoid激活函数。
具体来说,如图所示,通道注意通过空间全局池将前一个特征汇集到一个向量中,而空间注意通过通道池将特征汇集到单个特征映射中。我们发现这些方案在SR任务中效果较差,这需要像素级评估。另一方面,简单地删除池化操作可以显着提高性能。由于特征在像素级模型中相乘,我们将此修改后的注意力方案称为像素注意力,我们的网络称为像素注意力网络(PAN)。
3. 模型结构
模型结构主要是主分支的SC-PA模块(带有像素注意的自校准块)和重建分支的U-PA模块(像素注意的上采样块)。具体如下图所示:
SC-PA模块对输入先使用两个1
×
\times
× 1卷积进行分离,上层负责更高层次的特征操作且采用像素注意力PA,下层负责维护原始信息且采用标准卷积。
U-PA模块在卷积层之间引入PA,在以前的SR网络中,重建模块基本上由上采样和卷积层组成。 而且,很少有研究者调查过上采样阶段的注意力机制。 因此,在这项工作中,在重建模块中采用了PA层。 实验表明,引入PA可以显着提高最终性能,而参数成本却很少。 此外,本文还使用最近邻插值层(NN)作为上采样层以进一步节省参数。最终,U-PA块由NN、卷积层、PA、卷积层组成。
论文所提出的PAN专为高效的SR而设计,因此在网络架构中非常简洁。构建块SC-PA和U-PA也简单且易于实现。然而,当将此网络扩展到更大的规模(即 >50 个块)时,当前的结构将面临训练难度的问题。然后需要添加其他技术,如密集连接,以允许成功的训练。其次所提出的 PA方案还有另一个限制,通过实验发现,PA 对小型网络特别有用。但是随着网络规模的增加,有效性会降低。这主要是因为PA可以提高卷积的表达能力,这对于轻量级网络可能非常重要。相比之下,大规模网络是高度冗余的,它们的卷积没有得到充分利用,因此改进主要来自更好的训练策略。
4. 模型实现
4.1 像素注意力PA代码实现
class PA(nn.Module):
'''PA is pixel attention(像素注意力模块)
通过1x1卷积生成注意力权重,然后与原图相乘实现像素注意力机制
'''
def __init__(self, nf):
"""初始化
Args:
nf (int): 输入特征图的通道数
"""
super(PA, self).__init__()
# 1x1卷积层,用于生成注意力权重(不改变通道数)
self.conv = nn.Conv2d(nf, nf, 1)
# Sigmoid激活函数,将权重压缩到[0,1]范围
self.sigmoid = nn.Sigmoid()
def forward(self, x):
"""前向传播
Args:
x (Tensor): 输入特征图,形状为[batch, nf, height, width]
Returns:
Tensor: 加权后的特征图,形状与输入相同
"""
# 通过1x1卷积生成注意力权重
y = self.conv(x)
# 应用Sigmoid激活函数
y = self.sigmoid(y)
# 将原始特征图与注意力权重相乘(逐元素相乘)
out = torch.mul(x, y)
return out
# 给x和输出套conv(改进版的像素注意力模块)
class PAConv(nn.Module):
"""改进的像素注意力模块,增加了额外的卷积层
结构:
1. 使用1x1卷积生成注意力权重
2. 注意力权重与3x3卷积后的特征图相乘
3. 最后再通过一个3x3卷积
"""
def __init__(self, nf, k_size=3):
"""初始化
Args:
nf (int): 输入特征图的通道数
k_size (int, optional): 卷积核大小,默认为3
"""
super(PAConv, self).__init__()
# 1x1卷积层,用于生成注意力权重
self.k2 = nn.Conv2d(nf, nf, 1) # 1x1 convolution nf->nf
# Sigmoid激活函数
self.sigmoid = nn.Sigmoid()
# 第一个3x3卷积层(保持空间维度不变)
self.k3 = nn.Conv2d(nf, nf, kernel_size=k_size,
padding=(k_size - 1) // 2, bias=False) # 3x3 convolution
# 第二个3x3卷积层(保持空间维度不变)
self.k4 = nn.Conv2d(nf, nf, kernel_size=k_size,
padding=(k_size - 1) // 2, bias=False) # 3x3 convolution
def forward(self, x):
"""前向传播
Args:
x (Tensor): 输入特征图,形状为[batch, nf, height, width]
Returns:
Tensor: 处理后的特征图,形状与输入相同
"""
# 通过1x1卷积生成注意力权重
y = self.k2(x)
# 应用Sigmoid激活函数
y = self.sigmoid(y)
# 先对输入进行3x3卷积,然后与注意力权重相乘
out = torch.mul(self.k3(x), y)
# 对结果再进行一次3x3卷积
out = self.k4(out)
return out
4.2 SC-PA模块代码实现
class SCPA(nn.Module):
"""SCPA (Self-Calibrated Pixel Attention) 模块,改进自SCNet (Jiang-Jiang Liu et al. 2020)
论文: Improving Convolutional Networks with Self-Calibrated Convolutions
代码参考: https://github.com/MCG-NKU/SCNet
该模块通过自校准机制增强特征表示,结合了通道分割、空间卷积和像素注意力
"""
def __init__(self, nf, reduction=2, stride=1, dilation=1):
"""初始化
Args:
nf (int): 输入特征图的通道数
reduction (int, optional): 通道缩减比例,默认为2
stride (int, optional): 卷积步长,默认为1
dilation (int, optional): 空洞卷积的膨胀率,默认为1
"""
super(SCPA, self).__init__()
group_width = nf // reduction # 计算每组通道数
# 分支a的1x1卷积,用于通道缩减
self.conv1_a = nn.Conv2d(nf, group_width, kernel_size=1, bias=False)
# 分支b的1x1卷积,用于通道缩减
self.conv1_b = nn.Conv2d(nf, group_width, kernel_size=1, bias=False)
# 分支a的处理路径:3x3卷积
self.k1 = nn.Sequential(
nn.Conv2d(
group_width, group_width, kernel_size=3, stride=stride,
padding=dilation, dilation=dilation,
bias=False)
)
# 分支b的处理路径:像素注意力卷积模块
self.PAConv = PAConv(group_width)
# 合并后的1x1卷积,用于通道恢复
self.conv3 = nn.Conv2d(
group_width * reduction, nf, kernel_size=1, bias=False)
# LeakyReLU激活函数
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
def forward(self, x):
"""前向传播
Args:
x (Tensor): 输入特征图,形状为[batch, nf, height, width]
Returns:
Tensor: 处理后的特征图,形状与输入相同
"""
residual = x # 保存残差连接
# 分支a处理路径
out_a = self.conv1_a(x) # 1x1卷积通道缩减
out_b = self.conv1_b(x) # 1x1卷积通道缩减
# 对两个分支都应用LeakyReLU激活
out_a = self.lrelu(out_a)
out_b = self.lrelu(out_b)
# 分支a进一步处理:3x3卷积
out_a = self.k1(out_a)
# 分支b进一步处理:PAConv像素注意力
out_b = self.PAConv(out_b)
# 对两个分支的结果再次应用LeakyReLU激活
out_a = self.lrelu(out_a)
out_b = self.lrelu(out_b)
# 合并两个分支的结果(沿通道维度拼接)
out = self.conv3(torch.cat([out_a, out_b], dim=1))
# 添加残差连接
out += residual
return out
4.3 PAN模型代码实现
class PAN(nn.Module):
"""Pixel Attention Network (PAN) 主网络结构
该网络主要用于图像超分辨率任务,结合了SCPA模块和PA模块
结构概述:
1. 初始卷积层
2. 由多个SCPA模块组成的主干网络
3. 上采样部分(根据不同的放大倍数有不同的结构)
4. 最后的卷积层和残差连接
"""
def __init__(self, in_nc, out_nc, nf, unf, nb, scale=4):
"""初始化
Args:
in_nc (int): 输入通道数
out_nc (int): 输出通道数
nf (int): 主干网络的通道数
unf (int): 上采样部分的通道数
nb (int): 主干网络中SCPA模块的数量
scale (int, optional): 上采样倍数,默认为4
"""
super(PAN, self).__init__()
# 定义SCPA模块的partial函数,固定nf和reduction参数
SCPA_block_f = functools.partial(SCPA, nf=nf, reduction=2)
self.scale = scale # 上采样倍数
### 初始卷积层
self.conv_first = nn.Conv2d(in_nc, nf, 3, 1, 1, bias=True)
### 主干网络部分
# 由nb个SCPA模块组成的主干网络
self.SCPA_trunk = arch_util.make_layer(SCPA_block_f, nb)
# 主干网络后的卷积层
self.trunk_conv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
#### 上采样部分
# 第一层上采样卷积
self.upconv1 = nn.Conv2d(nf, unf, 3, 1, 1, bias=True)
# 第一层像素注意力模块
self.att1 = PA(unf)
# 第一层高分辨率卷积
self.HRconv1 = nn.Conv2d(unf, unf, 3, 1, 1, bias=True)
# 针对scale=4的特殊处理(两层上采样)
if self.scale == 4:
# 第二层上采样卷积
self.upconv2 = nn.Conv2d(unf, unf, 3, 1, 1, bias=True)
# 第二层像素注意力模块
self.att2 = PA(unf)
# 第二层高分辨率卷积
self.HRconv2 = nn.Conv2d(unf, unf, 3, 1, 1, bias=True)
# 最后的输出卷积层
self.conv_last = nn.Conv2d(unf, out_nc, 3, 1, 1, bias=True)
# LeakyReLU激活函数
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
def forward(self, x):
"""前向传播
Args:
x (Tensor): 输入图像,形状为[batch, in_nc, height, width]
Returns:
Tensor: 超分辨率输出图像,形状为[batch, out_nc, height*scale, width*scale]
"""
# 初始特征提取
fea = self.conv_first(x)
# 通过主干网络
trunk = self.trunk_conv(self.SCPA_trunk(fea))
# 残差连接
fea = fea + trunk
# 上采样部分(根据不同的scale有不同的处理)
if self.scale == 2 or self.scale == 3:
# 单层上采样
fea = self.upconv1(F.interpolate(fea, scale_factor=self.scale, mode='nearest'))
fea = self.lrelu(self.att1(fea)) # 像素注意力
fea = self.lrelu(self.HRconv1(fea)) # 高分辨率卷积
elif self.scale == 4:
# 两层上采样
fea = self.upconv1(F.interpolate(fea, scale_factor=2, mode='nearest'))
fea = self.lrelu(self.att1(fea)) # 第一层像素注意力
fea = self.lrelu(self.HRconv1(fea)) # 第一层高分辨率卷积
fea = self.upconv2(F.interpolate(fea, scale_factor=2, mode='nearest'))
fea = self.lrelu(self.att2(fea)) # 第二层像素注意力
fea = self.lrelu(self.HRconv2(fea)) # 第二层高分辨率卷积
# 最终输出
out = self.conv_last(fea)
# 残差连接:添加原始输入的上采样结果(双三次插值)
ILR = F.interpolate(x, scale_factor=self.scale, mode='bilinear', align_corners=False)
out = out + ILR
return out