拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch实战DeepLabv3+:从VOC到Cityscapes的语义分割全流程

PyTorch实战DeepLabv3+:从VOC到Cityscapes的语义分割全流程 简介本资源面向图像分割初学者与进阶开发者提供基于PyTorch在VOC与Cityscapes数据集上训练DeepLabv3的完整项目源码与流程教程帮助读者掌握语义分割从数据准备到模型部署的全链路实践。压缩包共43个文件约2.13MB以23个Python脚本为核心覆盖网络结构、数据集加载、损失函数、学习率调度与可视化等模块另含17张png效果图、2个txt依赖与说明文件及1个md文档目录按datasets、network、utils等分层组织便于按模块查阅。资源已吸引492人学习下载。读者可据此理解ASPP与解码器设计、空洞卷积原理掌握VOC与Cityscapes的预处理、训练、IoU评估及微调思路并借助可视化结果直观对比预测与标注快速积累多类别分割的实战经验。1. 从 VOC 到 CityscapesDeepLabv3 在 PyTorch 里到底解决了什么问题如果你手头有一批街景或室内场景图想把「人、车、路面、建筑」逐像素抠出来大概率会撞上两个现实问题一是 VOC 这种小分辨率、少类别数据集上跑得动的模型换到 Cityscapes 的 1024×2048 大图就显存爆炸二是同一套网络在 VOC 上 mIoU 能到 80 上下直接搬到 Cityscapes 却掉到 60 出头。DeepLabv3 就是冲着这两个矛盾来的——它用空洞卷积Atrous Convolution撑大感受野而不降分辨率用 ASPP 多尺度抓不同大小的目标再用一个轻量 Decoder 把底层边缘细节补回来。配合 PyTorch 的动态图和成熟的数据增强生态你可以在单卡 12GB 显存上把 VOC 训到收敛再用多尺度裁剪策略啃 Cityscapes。这篇笔记面向的是想自己复现、调参、踩坑的从业者不是来读论文摘要的。下面从环境、数据、模型、训练、排错一路讲到进阶技巧每一步都给可抄的命令和参数。2. 环境搭建与数据准备PyTorch、CUDA 和两套数据集的目录约定2.1 PyTorch 环境怎么装才不翻车先说结论不要用pip install torch裸装版本对不上 CUDA 是新手最常见的翻车点。我一般用 conda 建独立环境再按 PyTorch 官网的版本矩阵装。以 CUDA 11.8 为例conda create -n deeplab python3.9 -y conda activate deeplab # 按官网矩阵选对应 cuda 版本的 whl不要直接 pip install torch pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install numpy opencv-python pillow tqdm tensorboard逻辑说明python3.9是兼容性最稳的版本3.11 以上部分 torchvision 算子会报错。--index-url指向 CUDA 11.8 的官方 wheel 源避免装到 CPU 版。装完必须验证import torch print(torch.__version__, torch.cuda.is_available(), torch.version.cuda) # 期望输出类似 2.1.0 True 11.8如果cuda.is_available()返回 False先查驱动版本nvidia-smi驱动低于 450 就别硬上 CUDA 11.8。参数上torch.backends.cudnn.benchmark True在输入尺寸固定时能提速 10%~20%但输入尺寸变化频繁时反而变慢训练 Cityscapes 裁剪到固定 513×513 时可以开。2.2 VOC 与 Cityscapes 的目录结构统一两套数据集格式不同VOC 是JPEGImages SegmentationClass的 PNG 掩码Cityscapes 是leftImg8bit gtFine且标签是 trainId 映射后的灰度图。我一般统一成如下结构方便同一份 Dataset 代码复用datasets/ VOC2012/ JPEGImages/ # 原图 jpg SegmentationClass/ # 掩码 png像素值 0-20 ImageSets/Segmentation/train.txt, val.txt Cityscapes/ leftImg8bit/train/ # 原图 png gtFine/train/ # 掩码 png需转 trainIdCityscapes 原始gtFine的像素值是 0-33 的 labelId必须转成 0-18 的 trainId否则类别对不上mIoU 会莫名其妙低 20 个点。转换脚本核心逻辑import numpy as np from PIL import Image # Cityscapes labelId - trainId 映射-1 表示忽略 id_to_trainid {7:0,8:1,11:2,12:3,13:4,17:5,19:6,20:7,21:8,22:9, 23:10,24:11,25:12,26:13,27:14,28:15,31:16,32:17,33:18} def convert(mask_path, out_path): m np.array(Image.open(mask_path)) out np.full(m.shape, 255, dtypenp.uint8) # 255 作为 ignore_index for k, v in id_to_trainid.items(): out[m k] v Image.fromarray(out).save(out_path)参数说明255是 ignore_index训练时 CrossEntropyLoss 要设ignore_index255否则背景类会被错误计入。VOC 的掩码本身已是 0-20边界像素是 255同样处理。这一步不做后面所有指标都是错的属于典型的「黑匣子」式错误。3. DeepLabv3 网络结构ASPP、空洞卷积和 Decoder 三件套3.1 骨干网选 ResNet 还是 MobileNetDeepLabv3 本身是「骨干网 ASPP Decoder」的框架骨干网决定精度和速度的平衡。VOC 上我一般用 ResNet-101mIoU 能到 82 左右Cityscapes 上如果显存吃紧用 ResNet-50 或 MobileNetV2。关键改动是把 ResNet 最后两个 stage 的步长从 2 改成 1并用空洞卷积维持感受野输出 stride 从 32 降到 16 或 8。PyTorch 里可以这样改import torch.nn as nn from torchvision.models import resnet101 def make_backbone(output_stride16): model resnet101(weightsIMAGENET1K_V2) # 替换最后两个 stage 的 stride配合 dilation if output_stride 16: model.layer4[0].conv2.stride (1, 1) model.layer4[0].downsample[0].stride (1, 1) for m in model.layer4.modules(): if isinstance(m, nn.Conv2d) and m.kernel_size (3, 3): m.dilation (2, 2); m.padding (2, 2) return model逻辑说明output_stride16意味着最终特征图是输入的 1/16比默认的 1/32 保留更多空间信息对小目标和边界更友好。代价是显存和计算量上升约 1.5 倍。dilation2保证感受野不缩水。这一步改错ASPP 的多尺度就形同虚设。3.2 ASPP 的四个分支与全局池化ASPP 是 DeepLabv3 的核心用不同 dilation rate 的空洞卷积并行抓多尺度。标准配置是 1×1 卷积 三个 3×3 空洞卷积rate6,12,18 全局平均池化分支最后 concat 再 1×1 降维。rate 的选择和 output_stride 强相关output_stride16 时用 (6,12,18)output_stride8 时用 (12,24,36)。写错 rate 会导致大目标分割破碎。class ASPP(nn.Module): def __init__(self, in_ch2048, out_ch256, rates(6,12,18)): super().__init__() self.branches nn.ModuleList() self.branches.append(nn.Sequential( nn.Conv2d(in_ch, out_ch, 1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU())) for r in rates: self.branches.append(nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, paddingr, dilationr, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU())) self.gap nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_ch, out_ch, 1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU()) self.project nn.Sequential(nn.Conv2d(out_ch*5, out_ch, 1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU(), nn.Dropout(0.5)) def forward(self, x): size x.shape[-2:] feats [b(x) for b in self.branches] g nn.functional.interpolate(self.gap(x), sizesize, modebilinear, align_cornersFalse) feats.append(g) return self.project(torch.cat(feats, dim1))参数说明out_ch256是论文默认值显存紧张可降到 128。Dropout(0.5)在小数据集上防过拟合明显Cityscapes 这种大数据集可以降到 0.1 或关掉。全局池化分支补的是图像级上下文去掉它 mIoU 通常掉 1~2 个点。3.3 Decoder 怎么把底层特征接回来DeepLabv3 相比 v3 的最大改进就是加了 Decoder把骨干网 stride4 的底层特征拿过来1×1 卷积降到 48 通道和 ASPP 输出上采样 4 倍后 concat再 3×3 卷积 refine最后上采样回原图。底层特征带边缘和纹理对细长目标电线杆、栏杆提升明显。class Decoder(nn.Module): def __init__(self, low_ch256, mid_ch256, num_classes21): super().__init__() self.low_proj nn.Sequential(nn.Conv2d(low_ch, 48, 1, biasFalse), nn.BatchNorm2d(48), nn.ReLU()) self.fuse nn.Sequential( nn.Conv2d(48mid_ch, 256, 3, padding1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(), nn.Dropout(0.5), nn.Conv2d(256, 256, 3, padding1, biasFalse), nn.BatchNorm2d(256), nn.ReLU(), nn.Dropout(0.1)) self.cls nn.Conv2d(256, num_classes, 1) def forward(self, low, mid): low self.low_proj(low) mid nn.functional.interpolate(mid, sizelow.shape[-2:], modebilinear, align_cornersFalse) return self.cls(self.fuse(torch.cat([low, mid], dim1)))参数说明low_ch48是论文实验出来的甜点值太大反而稀释 ASPP 的语义信息。align_cornersFalse在 PyTorch 里是分割任务的标准选择设 True 会导致边缘错位半个像素累积起来 mIoU 掉 0.5 左右。4. 训练流程从 VOC 单卡跑通到 Cityscapes 多尺度裁剪4.1 VOC 上的最小可跑训练脚本先把 VOC 跑通再上 Cityscapes这是血泪经验。VOC 数据小、收敛快能快速验证数据管道和 loss 是否正确。核心训练循环import torch, torch.nn as nn from torch.utils.data import DataLoader from dataset import VOCSegDataset from model import DeepLabV3Plus device cuda model DeepLabV3Plus(num_classes21, backboneresnet101, output_stride16).to(device) criterion nn.CrossEntropyLoss(ignore_index255) optimizer torch.optim.SGD(model.parameters(), lr0.007, momentum0.9, weight_decay1e-4) scheduler torch.optim.lr_scheduler.PolynomialLR(optimizer, total_iters30000, power0.9) train_loader DataLoader(VOCSegDataset(datasets/VOC2012, splittrain, crop_size513), batch_size8, shuffleTrue, num_workers4, drop_lastTrue) model.train() for it, (img, mask) in enumerate(train_loader): img, mask img.to(device), mask.to(device) out model(img) out nn.functional.interpolate(out, sizemask.shape[-2:], modebilinear, align_cornersFalse) loss criterion(out, mask) optimizer.zero_grad(); loss.backward(); optimizer.step(); scheduler.step() if it % 50 0: print(fiter {it} loss {loss.item():.4f} lr {scheduler.get_last_lr()[0]:.5f}) if it 30000: break逻辑说明lr0.007是 DeepLab 系列的经典 poly 策略起点batch8 时稳定。PolynomialLR的power0.9让学习率平滑衰减比 StepLR 收敛更稳。ignore_index255必须和数据集掩码一致。输出上采样到 mask 尺寸再算 loss否则尺寸不匹配直接报错。VOC 上这套配置约 3 万 iteration 收敛mIoU 80 左右。4.2 Cityscapes 的裁剪、同步 BN 和类别平衡Cityscapes 原图 1024×2048直接整图训练 12GB 显存只能放 batch1BN 统计极不稳定。常见做法是随机裁剪 769×769 或 513×513batch 提到 8~16。如果多卡必须用SyncBatchNorm把 BN 统计跨卡同步model nn.SyncBatchNorm.convert_sync_batchnorm(model) model nn.parallel.DistributedDataParallel(model, device_ids[local_rank])参数说明单卡时不要用 SyncBN反而慢。Cityscapes 类别极不平衡road、building 占大头pole、traffic light 很少。我一般给稀有类加权# 19 类权重稀有类放大 2~3 倍 weights torch.tensor([1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0, 3.0,3.0,1.0,1.0,1.0,1.0,1.0,2.0,2.0]).to(device) criterion nn.CrossEntropyLoss(weightweights, ignore_index255)不加权的话pole 类 IoU 经常只有 30 出头拉低整体 mIoU。裁剪时用RandomCrop加RandomScale(0.5~2.0)和水平翻转Cityscapes 上能涨 2~3 个点。4.3 验证指标怎么算才可信mIoU 计算必须逐类累加混淆矩阵不能简单平均 batch 内的 IoU。正确做法def update_confusion(conf, pred, target, num_classes21, ignore255): mask target ! ignore pred, target pred[mask], target[mask] idx target * num_classes pred conf torch.bincount(idx, minlengthnum_classes**2).reshape(num_classes, num_classes) def compute_miou(conf): iou conf.diag() / (conf.sum(1) conf.sum(0) - conf.diag() 1e-10) return iou.mean().item(), iou参数说明conf是 num_classes×num_classes 的混淆矩阵行是真值、列是预测。1e-10防除零。验证时要把原图 resize 回原始尺寸再算否则小目标 IoU 虚高。这套指标和官方 Cityscapes 评测脚本对齐误差在 0.1 以内。5. 避坑与排查训练不收敛、显存爆炸、mIoU 虚低的真实原因5.1 loss 一直卡在 2.9 不降现象训练几百 iterationloss 稳定在 ln(21)≈3.04 附近不动。原因掩码像素值和 num_classes 不匹配比如 Cityscapes 没转 trainId掩码里有 33 这种值CrossEntropyLoss 直接忽略或报错。解决打印mask.max()和mask.unique()确认范围在 0~num_classes-1 或 255。VOC 掩码边界是 255Cityscapes 转换后也应是 255。5.2 显存爆炸但 batch 已经很小现象batch2 还 OOM。原因ASPP 的 dilation 太大导致中间特征图没降采样或者 Decoder 里 concat 后通道数翻倍。解决先确认output_stride16而不是 88 会多占约 40% 显存再把 ASPP 的out_ch从 256 降到 128最后开torch.cuda.amp混合精度scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): out model(img); loss criterion(out, mask) scaler.scale(loss).backward(); scaler.step(optimizer); scaler.update()混合精度通常省 30%~40% 显存mIoU 掉 0.2 以内值得开。5.3 mIoU 比论文低 10 个点现象VOC 只跑到 70。原因验证时没做多尺度或翻转增强或者忘了把模型切eval()导致 BN 用 batch 统计。解决验证前model.eval()用with torch.no_grad()推理时对 (0.75,1.0,1.25) 三个尺度加水平翻转取平均VOC 上能涨 3~4 个点。另外确认预训练权重加载成功骨干网没加载 ImageNet 权重会低 5 个点以上。5.4 边缘分割毛刺严重现象物体边界锯齿、断裂。原因Decoder 的底层特征没接或者上采样用了最近邻。解决确认 Decoder 输入是 stride4 的layer1输出而不是 stride8所有上采样用modebilinear, align_cornersFalse最后输出上采样到原图尺寸再 argmax不要在低分辨率 argmax 再放大。5.5 多卡训练比单卡还慢现象DDP 开了但吞吐没提升。原因num_workers 太小导致数据加载成瓶颈或者 SyncBN 在小 batch 下通信开销大。解决num_workers设成 CPU 核数的 1/2开pin_memoryTrue和persistent_workersTrue单卡 batch 小于 4 时别用 SyncBN。用torch.profiler看是数据加载还是前向慢别凭感觉调。6. 进阶技巧把 DeepLabv3 推到生产可用的几个手段训练收敛只是起点真正上线还要过推理速度和部署这两关。第一个技巧是推理加速把模型转 ONNX 再用 TensorRTCityscapes 1024×2048 单张从 200ms 降到 40ms 左右。导出时注意固定输入尺寸动态轴只留 batchtorch.onnx.export(model, torch.randn(1,3,513,513).cuda(), deeplabv3plus.onnx, input_names[input], output_names[output], dynamic_axes{input:{0:batch}, output:{0:batch}}, opset_version11)参数说明opset_version11对 interpolate 算子支持最好低于 11 上采样会报错。导出后务必用 onnxruntime 对齐一次输出误差超过 1e-3 说明有算子没对齐。第二个技巧是类别后处理。Cityscapes 里 road 和 sidewalk 经常混我一般对混淆矩阵里 IoU 最低的几类做形态学开闭运算或者用 CRF 后处理边界能干净不少mIoU 涨 1 个点左右。CRF 慢实时场景慎用。第三个技巧是难例挖掘。训练后期把验证集里 IoU 最低的 10% 图像挑出来过采样加入训练对 pole、traffic sign 这类小目标提升明显。我一般每 5000 iteration 做一次配合 poly 学习率整体能再涨 1.5 个点。最后一个习惯每次改完配置先跑 500 iteration 看 loss 曲线和验证 mIoU 趋势别一上来就训 3 万 iteration。我踩过最深的坑就是数据转换脚本写错白训了两天。现在固定流程是——转完数据先可视化 10 张掩码叠加图确认类别颜色对得上再开训。这个后悔药希望你不用吃。希望帮到你。本文还有配套的精品资源点击获取
返回列表