import torch # 导入 PyTorch 库
import torch.nn as nn # 导入 PyTorch 神经网络模块
import torch.nn.functional as F # 导入 PyTorch 函数模块
from torchvision.models.segmentation import deeplabv3_resnet50 # 从 torchvision 导入预训练的 DeepLabv3 ResNet50 模型
class DeepLabV3Plus(nn.Module): # 定义 DeepLabV3Plus 类,继承自 nn.Module
def __init__(self, num_classes=21, pretrained_backbone=True): # 初始化方法,参数包括类别数,默认值为 21,和是否使用预训练的骨干网络,默认为 True
super(DeepLabV3Plus, self).__init__() # 调用父类的初始化方法
self.deeplabv3 = deeplabv3_resnet50(pretrained_backbone=pretrained_backbone) # 使用预训练的 DeepLabv3 ResNet50 模型
self.deeplabv3.classifier = DeepLabHead(2048, num_classes) # 用自定义的 DeepLabHead 替换原有的分类器
def forward(self, x): # 定义前向传播方法
return self.deeplabv3(x) # 调用 deeplabv3 模型的前向传播
class DeepLabHead(nn.Sequential): # 定义 DeepLabHead 类,继承自 nn.Sequential
def __init__(self, in_channels, num_classes): # 初始化方法,参数包括输入通道数和类别数
super(DeepLabHead, self).__init__(
ASPP(in_channels, 256), # 添加 ASPP 模块
nn.Conv2d(256, 256, 3, padding=1, bias=False), # 添加卷积层
nn.BatchNorm2d(256), # 添加批量归一化层
nn.ReLU(), # 添加 ReLU 激活函数
nn.Conv2d(256, num_classes, 1) # 添加最后的卷积层,用于类别预测
)
class ASPP(nn.Module): # 定义 ASPP(空洞空间金字塔池化)类,继承自 nn.Module
def __init__(self, in_channels, out_channels, atrous_rates=None): # 初始化方法,参数包括输入通道数、输出通道数和空洞率列表
super(ASPP, self).__init__()
if atrous_rates is None: # 如果没有提供空洞率列表,则使用默认值
atrous_rates = [6, 12, 18]
layers = [] # 创建一个空列表,用于存放 ASPP 模块的层
# 添加一个卷积层、批量归一化层和 ReLU 激活函数
layers.append(nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU
))
for rate in atrous_rates: # 遍历空洞率列表
layers.append(ASPPConv(in_channels, out_channels, rate)) # 添加 ASPPConv 层,使用当前空洞率
self.convs = nn.ModuleList(layers) # 将 layers 列表转换为 ModuleList
self.global_pooling = nn.Sequential( # 定义全局平均池化层
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(in_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU()
)
self.out_conv = nn.Sequential( # 定义输出卷积层
nn.Conv2d(out_channels * (2 + len(atrous_rates)), out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU()
)
def forward(self, x): # 定义 ASPP 类的前向传播方法
x_pool = self.global_pooling(x) # 对输入 x 进行全局平均池化
x_pool = F.interpolate(x_pool, size=x.shape[2:], mode='bilinear', align_corners=False) # 将池化结果上采样到原始尺寸
x_aspp = [x_pool] + [conv(x) for conv in self.convs] # 对输入 x 应用 ASPPConv 层
x = torch.cat(x_aspp, dim=1) # 将上采样的全局池化结果和 ASPPConv 层的结果沿通道维度拼接
return self.out_conv(x) # 应用输出卷积层
class ASPPConv(nn.Sequential): # 定义 ASPPConv 类,继承自 nn.Sequential
def __init__(self, in_channels, out_channels, dilation): # 初始化方法,参数包括输入通道数、输出通道数和空洞率
super(ASPPConv, self).__init__(
nn.Conv2d(in_channels, out_channels, 3, padding=dilation, dilation=dilation, bias=False), # 添加带空洞的卷积层
nn.BatchNorm2d(out_channels), # 添加批量归一化层
nn.ReLU() # 添加 ReLU 激活函数
)
if __name__ == '__main__':
model = DeepLabV3Plus(num_classes=21) # 创建一个 DeepLabV3Plus 模型实例,指定 21 个类别
print(model) # 输出模型结构
假设,输入数据是512x512x3,主干网络选用mobilenetv2。结合代码库,分析数据在模型中的各个模块中是如何运行的,以及输入输出的尺寸。
引用:https://github.com/bubbliiiing/deeplabv3-plus-pytorch
在输入图像的尺寸为 512 × 512 × 3 512 \times 512 \times 3 512×512×3 的情况下,DeepLabV3+的工作流程如下:
让我们分析一下代码和DeepLabv3+的架构图。
输入: 输入图片的尺寸为 512 × 512 × 3 512 \times 512 \times 3 512×512×3。
MobileNetV2: 在 mobilenetv2.py
文件中,我们可以看到定义了 MobileNetV2
类。在这个类中,我们可以看到一系列的卷积操作。每一次卷积操作后,都有一个 stride
参数,表示卷积的步长。步长影响了输出特征图的尺寸:步长为2的卷积会将特征图的尺寸减半。在MobileNetV2的定义中,我们可以看到一共有5个步长为2的卷积,因此输出特征图的尺寸是输入的 2 5 = 32 2^5=32 25=32 分之一,也就是 (512/32=16),即 (16 \times 16)。同时,输出的通道数为 last_channel
,这个值在初始化 MobileNetV2
类时被设置为1280。
ASPP: 在 deeplab.py
文件中,我们可以看到ASPP模块的定义。ASPP模块对输入的特征图进行一系列的空洞卷积操作,然后将结果沿通道维度拼接起来。这个过程不会改变特征图的高度和宽度,但会增加通道数。
解码器: 解码器部分首先进行一个 4 × 4 4 \times 4 4×4 的上采样,然后将结果与主干网络的某个中间层特征图进行拼接,最后再进行一次 4 × 4 4 \times 4 4×4 的上采样。因此,解码器的输出尺寸是输入的 4 × 4 = 16 4 \times 4 = 16 4×4=16 倍,即 16 × 16 × 16 = 256 × 256 16 \times 16 \times 16 = 256 \times 256 16×16×16=256×256。
最终输出: 通过一个 2 × 2 2 \times 2 2×2 的上采样层,我们可以将解码器的输出恢复到输入图像的尺寸,即 256 × 2 = 512 × 512 256 \times 2 = 512 \times 512 256×2=512×512。
总结一下,每个模块的输入输出尺寸分别为:
这个分析是基于MobileNetV2步长为32的假设,具体的值还需要查看训练脚本中的配置。这也是为什么我们在解码器的输出尺寸分析中使用了 4 × 4 4 \times 4 4×4 和 2 × 2 2 \times 2 2×2 的上采样,而不是 16 × 16 16 \times 16 16×16 和 4 × 4 4 \times 4 4×4。