从V1到V3+:手把手带你复现Deeplab系列语义分割模型(PaddlePaddle 2.2.1版)

张开发
2026/4/15 21:18:28 15 分钟阅读

分享文章

从V1到V3+:手把手带你复现Deeplab系列语义分割模型(PaddlePaddle 2.2.1版)
从V1到V3手把手带你复现Deeplab系列语义分割模型PaddlePaddle 2.2.1版语义分割作为计算机视觉领域的核心任务之一正在自动驾驶、医疗影像分析等领域发挥越来越重要的作用。而Deeplab系列模型作为该领域的标杆性工作其演进历程堪称一部语义分割技术进化史。本文将带您用PaddlePaddle 2.2.1框架从第一行代码开始完整复现这个经典家族的每个版本不仅会看到空洞卷积、ASPP等创新如何逐步提升模型性能更将通过可运行的代码示例让您深入理解每个技术改进的实际实现方式。1. 环境准备与基础概念在开始构建模型之前我们需要确保开发环境正确配置。推荐使用Python 3.7和PaddlePaddle 2.2.1版本这是本教程测试通过的环境组合。安装命令如下pip install paddlepaddle-gpu2.2.1.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html语义分割与普通图像分类的关键区别在于它需要对图像中的每个像素进行分类预测。这就带来了两个核心挑战位置信息保留传统CNN通过池化层逐步降低分辨率会丢失精细的空间信息多尺度处理同一类物体可能以不同尺寸出现在图像中Deeplab系列的创新正是围绕解决这些问题展开的。我们先看一个简单的语义分割模型结构示例import paddle import paddle.nn as nn class BasicSegModel(nn.Layer): def __init__(self, num_classes10): super().__init__() self.encoder nn.Sequential( nn.Conv2D(3, 64, 3, padding1), nn.ReLU(), nn.MaxPool2D(2) ) self.decoder nn.Sequential( nn.Conv2D(64, num_classes, 1), nn.Upsample(scale_factor2, modebilinear) ) def forward(self, x): x self.encoder(x) x self.decoder(x) return x这个基础模型已经包含了编码器-解码器的基本结构但存在明显的分辨率损失问题。接下来我们将看到Deeplab系列如何通过一系列创新来解决这些问题。2. Deeplabv1空洞卷积的首次应用Deeplabv1发表于2014年其两大核心创新是空洞卷积Atrous Convolution在不增加参数量的情况下扩大感受野CRF后处理优化模型输出的空间一致性让我们重点看看空洞卷积的实现。在PaddlePaddle中可以通过设置dilation参数来实现# 普通3x3卷积 normal_conv nn.Conv2D(64, 128, 3, padding1) # dilation2的空洞卷积 atrous_conv nn.Conv2D(64, 128, 3, padding2, dilation2)感受野计算是理解空洞卷积的关键。对于3×3卷积核不同dilation rate对应的等效卷积核尺寸为dilation rate等效核尺寸感受野增加量13×3225×5449×98Deeplabv1的完整实现需要注意几个关键点使用VGG16作为主干网络但修改最后两个池化层的步长为1将全连接层转换为卷积层FCN思想在输出端添加CRF后处理模块以下是主干网络的关键修改代码def modify_vgg(): model paddle.vision.models.vgg16(pretrainedTrue) # 修改最后两个池化层 model.features[30].stride 1 # pool4 model.features[30].padding 1 model.features[23].stride 1 # pool3 return model注意在实际应用中CRF后处理由于计算成本较高在后续版本中逐渐被淘汰但在v1中仍是重要组成部分。3. Deeplabv2ASPP模块的引入Deeplabv2的最大创新是提出了空洞空间金字塔池化ASPP通过并行使用不同dilation rate的空洞卷积来捕获多尺度信息。ASPP的结构可以用以下代码实现class ASPPModule(nn.Layer): def __init__(self, in_channels, out_channels, rates): super().__init__() self.branches nn.LayerList() # 1x1卷积分支 self.branches.append( nn.Sequential( nn.Conv2D(in_channels, out_channels, 1), nn.BatchNorm(out_channels), nn.ReLU() ) ) # 不同rate的空洞卷积分支 for r in rates: self.branches.append( nn.Sequential( nn.Conv2D(in_channels, out_channels, 3, paddingr, dilationr), nn.BatchNorm(out_channels), nn.ReLU() ) ) # 全局平均池化分支 self.branches.append( nn.Sequential( nn.AdaptiveAvgPool2D(1), nn.Conv2D(in_channels, out_channels, 1), nn.BatchNorm(out_channels), nn.ReLU() ) ) def forward(self, x): outputs [] for branch in self.branches: out branch(x) if isinstance(branch[-1], nn.AdaptiveAvgPool2D): out nn.functional.interpolate(out, sizex.shape[2:], modebilinear) outputs.append(out) return paddle.concat(outputs, axis1)Deeplabv2还引入了ResNet作为主干网络显著提升了特征提取能力。与v1相比v2在Pascal VOC 2012测试集上的mIOU从71.6%提升到了79.7%。各版本性能对比版本主干网络mIOU (%)关键创新v1VGG1671.6空洞卷积、CRFv2ResNet5079.7ASPP模块v3ResNet10185.7改进ASPP、串并联结构v3Xception89.0编解码结构、深度可分离卷积4. Deeplabv3ASPP的优化与多网格策略Deeplabv3对ASPP模块做了重要改进增加了批量归一化层引入了图像级特征全局平均池化采用了串行和并行相结合的结构一个关键的实现细节是多网格Multi-Grid策略即在残差块内部使用不同dilation rate的组合。例如class ResidualBlock(nn.Layer): def __init__(self, in_channels, out_channels, stride1, dilations[1,2,4]): super().__init__() self.convs nn.LayerList() for d in dilations: self.convs.append( nn.Sequential( nn.Conv2D(in_channels, out_channels, 3, paddingd, dilationd, stridestride), nn.BatchNorm(out_channels), nn.ReLU() ) ) self.shortcut nn.Conv2D(in_channels, out_channels, 1) if in_channels ! out_channels else None def forward(self, x): residual x for conv in self.convs: x conv(x) if self.shortcut: residual self.shortcut(residual) return nn.functional.relu(x residual)在训练过程中学习率策略也需要注意。推荐使用多项式衰减def create_optimizer(model): scheduler paddle.optimizer.lr.PolynomialDecay( learning_rate0.01, decay_steps10000, end_lr0.0001, power0.9 ) return paddle.optimizer.Momentum( learning_ratescheduler, parametersmodel.parameters(), momentum0.9, weight_decay4e-5 )5. Deeplabv3编解码结构与Xception主干Deeplabv3是当前最先进的版本主要改进包括引入编码器-解码器结构增强边缘信息采用Xception作为主干网络使用深度可分离卷积减少计算量Xception模块的关键实现class DepthwiseSeparableConv(nn.Layer): def __init__(self, in_channels, out_channels, kernel_size3, stride1, dilation1): super().__init__() self.depthwise nn.Conv2D( in_channels, in_channels, kernel_size, stridestride, paddingdilation, dilationdilation, groupsin_channels ) self.pointwise nn.Conv2D(in_channels, out_channels, 1) def forward(self, x): x self.depthwise(x) x self.pointwise(x) return x完整的编解码结构实现要点class Deeplabv3Plus(nn.Layer): def __init__(self, num_classes): super().__init__() # 编码器部分 self.encoder XceptionBackbone() self.aspp ASPPModule(2048, 256, [6,12,18]) # 解码器部分 self.decoder nn.Sequential( nn.Conv2D(256 48, 256, 3, padding1), # 48是低级特征通道数 nn.BatchNorm(256), nn.ReLU(), nn.Conv2D(256, num_classes, 1) ) def forward(self, x): # 编码过程 low_level_feat self.encoder.get_low_level_feat(x) x self.encoder(x) x self.aspp(x) # 解码过程 x nn.functional.interpolate(x, scale_factor4, modebilinear) x paddle.concat([x, low_level_feat], axis1) x self.decoder(x) x nn.functional.interpolate(x, scale_factor4, modebilinear) return x在实际项目中我发现Xception主干的预训练权重对模型性能影响很大。使用在ImageNet上预训练的权重进行初始化通常能使模型收敛更快、效果更好。6. 训练技巧与实战建议要让Deeplab模型发挥最佳性能还需要注意以下实践细节数据增强策略随机缩放0.5-2.0倍左右翻转颜色抖动随机裁剪通常裁剪尺寸为513×513train_transforms paddle.vision.transforms.Compose([ paddle.vision.transforms.RandomHorizontalFlip(), paddle.vision.transforms.RandomResizedCrop((513,513), scale(0.5,2.0)), paddle.vision.transforms.ColorJitter( brightness0.5, contrast0.5, saturation0.5), paddle.vision.transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])损失函数选择常用交叉熵损失可添加辅助损失auxiliary loss帮助训练类别不平衡时可使用加权交叉熵class WeightedCrossEntropy(nn.Layer): def __init__(self, weights): super().__init__() self.weights paddle.to_tensor(weights) def forward(self, pred, label): log_softmax nn.functional.log_softmax(pred, axis1) label_one_hot nn.functional.one_hot(label, pred.shape[1]) loss - (self.weights * label_one_hot * log_softmax).sum(axis1) return loss.mean()评估指标mIOU平均交并比是最常用指标像素准确率各类别的IOUdef compute_miou(pred, label, num_classes): pred pred.argmax(axis1) miou 0 for i in range(num_classes): pred_mask pred i label_mask label i intersection (pred_mask label_mask).astype(float32).sum() union (pred_mask | label_mask).astype(float32).sum() iou (intersection 1e-6) / (union 1e-6) miou iou return miou / num_classes在Cityscapes数据集上的训练过程中我发现学习率预热warmup能显著提升模型稳定性。具体做法是在前500次迭代中线性增加学习率然后再开始衰减。

更多文章