PAN
PAN全称Path Aggregation Network,是由Megvii在2018年提出的一种处理多尺度问题的方法。
PAN(Path Aggregation Network)是一个用于图像语义分割的深度神经网络架构。PAN的主要思路是通过聚合来自不同层级的特征图,使得每个特征图中的信息都可以被充分利用,从而提高检测精度。与FPN类似,PAN也是一种金字塔式的特征提取网络,但是它采用的是自下而上的特征传播方式。
PAN的构建方式是从低分辨率的特征图开始向上采样,同时从高分辨率的特征图开始向下采样,将它们连接起来形成一条路径。在这个过程中,每一层特征图的信息都会与上下相邻层的特征图融合,但与FPN不同的是,PAN会将不同层级的特征图融合后的结果进行加和,而不是级联。这样可以避免在级联过程中信息的损失,同时还可以保留更多的细节信息,从而提高检测精度。
在PAN中,网络的主干部分通常采用ResNet等常用的卷积神经网络结构。在主干网络的后半部分,PAN引入了一个自下而上的侧边分支,用于将低分辨率的特征图传递到高分辨率的层中。这个侧边分支与主干网络是平行的,由一系列卷积和上采样(即反卷积)操作组成,从而将低分辨率的特征图上采样到与高分辨率的特征图相同的分辨率。
在将不同分辨率的特征图进行融合时,PAN采用了一种类似于FPN的方法,但稍有不同。具体而言,PAN中首先将低分辨率的特征图进行上采样,然后将其与高分辨率的特征图进行拼接,得到一个更加丰富的特征图。接着,对这个特征图进行卷积操作,以得到最终的特征表示。
与FPN相比,PAN中自下而上的特征传播方式更为高效,可以在更少的计算资源下实现更好的语义分割效果。同时,PAN中的特征融合方式也具有一定的优势,能够更好地保留低分辨率特征图中的细节信息,从而提高分割的准确性。
如图所示,b区域是PAN多出的一条自底向上的路径。
代码复现:
import torch
import torch.nn as nn
import torchvision.models as models
import torch.nn.functional as F
class ConvBlock(nn.Module):
# 定义一个名为__init__的构造函数,这是一个特殊的方法,在创建类的新实例时自动调用
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
# 调用父类(即nn.Module)的构造函数,这是在初始化对象时必须要做的
super(ConvBlock, self).__init__()
# 创建一个2D卷积层,输入通道数为in_channels,输出通道数为out_channels,卷积核大小为kernel_size
# stride默认为1,padding默认为0,bias=False表示不使用偏置项
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False)
# 创建一个批量归一化层,输入通道数为out_channels
self.bn = nn.BatchNorm2d(out_channels)
# 创建一个ReLU激活函数,inplace=True表示原地操作,即不创建新的张量,直接在原张量上进行操作
self.relu = nn.ReLU(inplace=True)
# 定义一个名为forward的前向传播函数,这个函数定义了数据在网络中的流动方式
def forward(self, x):
# 将输入x通过卷积层和批量归一化层,然后通过ReLU激活函数
return self.relu(self.bn(self.conv(x)))
# 定义一个名为PAN的类,继承自nn.Module,创建一个新的PyTorch模型
class PAN(nn.Module):
def __init__(self, num_classes): # 定义类的初始化函数,接收一个参数num_classes,代表分类的类别数量
super(PAN, self).__init__() # 调用父类nn.Module的初始化函数
# 定义底部通路(自下而上)的侧边层次结构
# Define the lateral layers for bottom-up pathway
self.lateral_p2 = ConvBlock(256, 256, kernel_size=1) # 创建一个ConvBlock层,输入通道数为256,输出通道数为256,卷积核大小为1
self.lateral_p3 = ConvBlock(512, 256, kernel_size=1) # 创建一个ConvBlock层,输入通道数为512,输出通道数为256,卷积核大小为1
self.lateral_p4 = ConvBlock(1024, 256, kernel_size=1) # 创建一个ConvBlock层,输入通道数为1024,输出通道数为256,卷积核大小为1
self.lateral_p5 = ConvBlock(2048, 256, kernel_size=1) # 创建一个ConvBlock层,输入通道数为2048,输出通道数为256,卷积核大小为1
# 定义顶部通路(自上而下)的层次结构
# Define the layers for the top-down pathway
self.topdown_p4 = ConvBlock(256, 256, kernel_size=3, padding=1) # 创建一个ConvBlock层,输入通道数为256,输出通道数为256,卷积核大小为3,padding为1
self.topdown_p3 = ConvBlock(256, 256, kernel_size=3, padding=1) # 创建一个ConvBlock层,输入通道数为256,输出通道数为256,卷积核大小为3,padding为1
self.topdown_p2 = ConvBlock(256, 256, kernel_size=3, padding=1) # 创建一个ConvBlock层,输入通道数为256,输出通道数为256,卷积核大小为3,padding为1
# 定义最终预测的层次结构
# Define the final prediction layer
self.final_pred = nn.Conv2d(256, num_classes, kernel_size=3, padding=1) # 创建一个卷积层,输入通道数为256,输出通道数为num_classes(分类类别数),卷积核大小为3,padding为1
def forward(self, p2, p3, p4, p5): # 定义前向传播函数,接收四个参数p2、p3、p4、p5,代表不同阶段的特征图
# 应用侧边卷积块
# Apply the lateral convolutions
p2_lateral = self.lateral_p2(p2) # 对p2进行侧边卷积操作
p3_lateral = self.lateral_p3(p3) # 对p3进行侧边卷积操作
p4_lateral = self.lateral_p4(p4) # 对p4进行侧边卷积操作
p5_lateral = self.lateral_p5(p5) # 对p5进行侧边卷积操作
# 构建自上而下的路径
# 这一部分是模型中一个重要的部分,它将不同层级的特征图自上而下地结合起来,增强了模型在不同尺度上的特征表达能力
# Build the top-down pathway
p4_topdown = self.topdown_p4(F.interpolate(p5_lateral, scale_factor=2, mode='nearest') + p4_lateral)
p3_topdown = self.topdown_p3(F.interpolate(p4_topdown, scale_factor=2, mode='nearest') + p3_lateral)
p2_topdown = self.topdown_p2(F.interpolate(p3_topdown, scale_factor=2, mode='nearest') + p2_lateral)
# 输出最终的预测结果
# 通过最后一个卷积层,将最后的特征图转化为预测结果
# Output the final prediction
pred = self.final_pred(p2_topdown)
return pred
# 定义一个名为ResNetBackbone的类,它继承自torch.nn.Module类,表明它是一个神经网络模块。
class ResNetBackbone(nn.Module):
# 定义类的初始化函数,当创建ResNetBackbone类的实例时,这个函数会被调用。
def __init__(self):
# 调用父类的初始化函数,这是必须的,它会初始化一些基本的属性。
super(ResNetBackbone, self).__init__()
# 创建一个预训练的ResNet模型,ResNet是一种流行的深度学习模型,它用于图像识别、物体检测等任务。
resnet = models.resnet50(pretrained=True) # 使用预训练的ResNet-50模型。
# 创建一个nn.Sequential模块,它是一个线性容器,可以将多个模块按顺序连接起来。
# 这里的*list(resnet.children())[:4]会获取预训练ResNet模型的前四个children,也就是前四个层。
self.layer0 = nn.Sequential(*list(resnet.children())[:4]) # 初始层到maxpool # 创建"layer0"这个部分,包含了预训练模型的初始几层。
# 将layer1层保存为self.layer1,这样可以在之后使用。
self.layer1 = resnet.layer1 # layer1 # 创建"layer1"这个部分,包含了预训练模型的layer1层。
# 将layer2层保存为self.layer2,这样可以在之后使用。
self.layer2 = resnet.layer2 # layer2 # 创建"layer2"这个部分,包含了预训练模型的layer2层。
# 将layer3层保存为self.layer3,这样可以在之后使用。
self.layer3 = resnet.layer3 # layer3 # 创建"layer3"这个部分,包含了预训练模型的layer3层。
# 将layer4层保存为self.layer4,这样可以在之后使用。
self.layer4 = resnet.layer4 # layer4 # 创建"layer4"这个部分,包含了预训练模型的layer4层。
# 定义前向传播函数,这个函数定义了数据在网络中的流动方式。
def forward(self, x):
# 将输入x通过layer0部分(即初始几层和maxpool层)。
x = self.layer0(x) # 通过"layer0"进行前向传播。
# 将x通过layer1层。
p2 = self.layer1(x) # 通过"layer1"进行前向传播,结果保存在p2中。
# 将p2通过layer2层。
p3 = self.layer2(p2) # 通过"layer2"进行前向传播,结果保存在p3中。
# 将p3通过layer3层。
p4 = self.layer3(p3) # 通过"layer3"进行前向传播,结果保存在p4中。
# 将p4通过layer4层。
p5 = self.layer4(p4) # 通过"layer4"进行前向传播,结果保存在p5中。
# 返回p2、p3、p4和p5四个结果。在实际应用中,可能会根据需要选择其中一个或多个输出。
return p2, p3, p4, p5 # 返回p2, p3, p4, p5四个结果。
# 实例化ResNetBackbone
resnet_backbone = ResNetBackbone()
# 实例化PAN模型,这里我们用21个类别作为示例
pan_model = PAN(num_classes=21)
# 一个模拟的输入图片,大小为[batch_size, channels, height, width]
input_image = torch.rand(8, 3, 224, 224) # 假设输入图片的尺寸为224x224
# 使用Backbone网络提取特征
with torch.no_grad(): # 在提取特征时不计算梯度
p2, p3, p4, p5 = resnet_backbone(input_image)
# 将提取的特征图输入到PAN模型中进行前向传播
output = pan_model(p2, p3, p4, p5)
# 打印输出的形状
print('Output shape:', output.shape)