ARTICLE DETAIL

资讯详情

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

LiteSeg轻量语义分割网络:PyTorch实现与部署实践

LiteSeg轻量语义分割网络:PyTorch实现与部署实践 简介LiteSeg实时轻量级语义分割算法的PyTorch实现面向需要在边缘设备、低功耗硬件上完成实时推理的算法工程师与研究者适用于自动驾驶、无人机监控、医疗影像分析等像素级分类场景。压缩包共39个文件以21个Python源文件为主覆盖模型定义、训练、评估与ONNX转换全流程另有样例图片、配置文件和预训练权重方便直接运行验证整包约21.09MB。目前已有1358人下载学习。资源内置MobileNet、ShuffleNet、DarkNet等多种backbone变体以及深度可分离卷积、ASPP、边缘增强等关键模块同时提供数据增强、focal loss、在线难例挖掘、IOU评估、标签生成等工程化脚本可直接替换数据集进行训练调优也适合二次开发部署。目录结构清晰代码注释规范对入门轻量分割和工程落地都很有参考价值。1. 实时语义分割的算力困境与 LiteSeg 的切入点在嵌入式设备、移动端 SoC 上做逐像素分类最常见的矛盾是精度达标的模型跑不动能跑起来的模型分割边界又糊成一团。DeeplabV3 这类经典架构在 1080Ti 上可以做到实时但换到 Jeston Nano 或者手机 NPU 上单帧延迟立刻涨到几百毫秒根本无法接入视频流。LiteSeg 正是冲着这个场景去的——在 PyTorch 框架下用轻量化主干替换标准 ResNet配合空洞空间金字塔池化保留多尺度感受野在参数量和 mIoU 之间找平衡点。它的 v2 版本把主干可选范围扩展到 MobileNet、ShuffleNet、DarkNet 三系配套训练、评估、ONNX 导出脚本齐全适合两类人一类是刚入门语义分割、想研究轻量网络怎么设计的研究生另一类是已经在做边缘端部署、需要快速评估不同主干精度与延迟的工程师。2. LiteSeg 网络架构与轻量化设计选型2.1 编码器解码器框架与轻量主干的组合逻辑LiteSeg-v2 源码仓库里的结构很清晰liteseg.py是整体的前向逻辑backbone_networks目录下分别给出了liteseg_mobilenet.py、liteseg_shufflenet.py和liteseg_darknet.py三个文件各自的命名暗示了主干网络的三种选择。这种设计思路是标准的两段式语义分割模型编码器负责从输入图像提取高维语义特征解码器负责将低分辨率特征图恢复成原分辨率预测。解码端在这里并不是论文里那种复杂的逐步上采样而是依赖aspp.py提供的多尺度空洞卷积特征聚合——整体结构参考了 DeepLab 系列的思路但把主干替换成了推理开销更小的分类网络。选择轻量主干的时候首先要确认的是替换的成本边界。MobileNetV2 的倒残差结构在 ImageNet 上大约 70% 出头的 top-1 精度比 ResNet50 低 5 个点左右但 FLOPs 不到 ResNet50 的六分之一。这个精度的下降在分割任务上体现为特征图语义信息密度的降低最终影响 mIoU但换来的是视频流场景能跑满帧率。具体到 LiteSeg 的代码liteseg_shufflenet.py里的 ShuffleNetV2 利用 channel shuffle 增强跨通道信息交互它的优势在于实际推理速度因为逐通道卷积在底层硬件上更容易被优化。DarkNet 分支则偏向精度参数量介于前两者之间。2.2 ASPP 空洞空间金字塔池化的参数设计aspp.py是全项目的核心组件它负责在多个空洞率下并行采样特征。常见做法是在 ASPP 模块里设置四个分支一个 1x1 卷积做降维、三个 3x3 空洞卷积分别使用 6、12、18 的空洞率再加一个全局平均池化分支。源文件aspp.py中这几个空洞率的取值直接决定了感受野覆盖范围。空洞率 6 的分支侧重局部细节12 覆盖中等范围的上下文18 负责捕捉大目标整体结构最后池化分支提供整图级别的统计信息。这里有一个在资源包在训练和推理时需要特别注意的参数设置输入分辨率变化时ASPP 的感受野是等比放大的。inputs尺寸如果从 512x512 改到 768x768空洞率 18 对应的采样范围增加对大一倍的目标依然有覆盖但小目标的分割可能因为背景信息过多而出现误分类。跑train.py之前如果自己改了crop_size建议同步检查 aspp dilation 是否需要缩放。2.3 深度可分离卷积的实现考量separableconv.py提供了逐通道卷积加逐点卷积的封装这是 MobileNetV2 的基础构建单元。一个标准 3x3 卷积在输入输出通道均为 C 时参数是 9 * C * C换成深度可分离卷积后逐通道卷积参数 9 * C逐点卷积参数 C * C总参数量从 9C² 降到 C² 9C当 C 是 256 时计算量约为原来的七分之一。源码里需要留意的是逐通道卷积的参数组设置PyTorch 中实现时groups必须等于输入通道数# separableconv.py 的核心结构示意 import torch.nn as nn class SeparableConv2d(nn.Module): def __init__(self, in_channels, out_channels, kernel_size3, stride1, padding1): super().__init__() # depthwise 卷积groupsin_channels 意味着每个输入通道单独卷 self.depthwise nn.Conv2d( in_channels, in_channels, kernel_sizekernel_size, stridestride, paddingpadding, groupsin_channels, biasFalse ) # pointwise 卷积只做 1x1 通道融合 self.pointwise nn.Conv2d( in_channels, out_channels, kernel_size1, biasTrue ) def forward(self, x): return self.pointwise(self.depthwise(x))每组卷积groupsin_channels意味着没有跨通道的信息融合这使得模型前几层的语义抽象能力变弱。在训练 LiteSeg 时如果发现收敛速度明显变慢优先检查separableconv.py里 depthwise 卷积之后是否接了 BatchNorm 和激活函数这比标准卷积更依赖 BN 来稳定分布。2.4 三种主干的取舍建议仓库liteseg_darknet.py与liteseg_mobilenet.py在精度上的差距大约在 2 到 4 个 mIoU但 DarkNet 系列的推理耗时会高出 30% 以上。以下是对比维度主干网络核心构建块相对推理速度适合设备MobileNetV2倒残差 深度可分离最快手机 SoC、嵌入式 NPUShuffleNetV2channel shuffle 分组卷积较快移动端 GPUDarkNet标准卷积 cross-stage 连接较慢Jetson 等带 CUDA 的设备区分这三种结构有一个快速判断标准看backbone_networks中对应文件里Conv2d的groups参数如果groups等于输入通道数就是深度可分离变体等于输入通道数一半则通常是 ShuffleNet 风格的分组卷积。这三条路并不冲突实际项目里可以先用 MobileNet 跑通训练流程再用 DarkNet 微调验证上限。3. PyTorch 训练管线数据增强、损失函数与训练配置3.1 augmentation 与数据集加载的细节实现常规分割训练里的增强策略与检测任务不同不能随便做随机的方形裁剪因为标签图必须和输入做完全相同的空间变换。dataloaders/augmentation.py中需要同时处理 image 和 mask 两个数组最常用的组合是随机水平翻转、尺度抖动和颜色抖动。我在实际跑训练时会固定使用以下配置随机缩放比例在 0.5 到 2.0 之间缩放后再随机裁剪到固定输入尺寸最后以 0.5 概率水平翻转。这样做的好处是模型对目标尺度变化不敏感在道路场景中物体大小差异非常大纯固定分辨率训练出来的模型换到新环境往往要重新调。彩色图像的均值方差统计对收敛速度有直接影响。PyTorch 的transforms.Normalize接收三个通道的 mean 和 stdLiteSeg 的utils.py里如果沿用 ImageNet 的均值[0.485, 0.456, 0.406]加载自己采集的数据集时也需要保持。这里有一个很多人忽略的坑使用 ImageNet 预训练权重的话输入归一化标准必须和预训练时保持一致否则浅层特征分布错位前几个 epoch 的 loss 会跳得厉害且很难恢复。# 数据集目录结构建议 datasets/ ├── train.txt # 训练样本相对路径列表 ├── val.txt # 验证样本相对路径列表 ├── JPEGImages/ # 原图 └── SegmentationClass/ # 伪彩色标签 或 灰度标签如果训练自己的数据parse_classes.py负责把类别名称映射到固定索引注意类别顺序一旦确定就不要改动否则评估脚本iou_eval.py计算出的类别 AP 全部错位。3.2 损失函数选择与 OHEM 的适用边界项目里loss.py的实现决定训练收敛走向。语义分割最常见的是交叉熵损失但城市道路场景里类别极度不均衡比如树木和天空占比很大、摩托车和交通标志占比很少。直接用交叉熵会导致模型把所有像素预测为占比最大的类别也能拿到很低的 loss。此时有两种主流改进方案一是用focal loss降低易分类样本的权重二是用在线难样本挖掘 OHEM两者处理的侧重不同。OHEM 的做法是每个 batch 计算完所有像素的损失之后按损失值从大到小排序只取前 N 个像素做反向传播。这个 N 通常设为 batch 内总像素数的一半也就是 50% 的 top-k 比例。在源代码中可以通过判断 loss 的topk部分完成但要注意 OHEM 有一个副作用如果某个类别正好在难样本中占绝大比例模型可能被带偏变得更关注那一个类别。训练时如果发现验证集上某个小类别完全预测不出来先关闭 OHEM 换成带类别权重的交叉熵权重的设置公式一般取出现频率的倒数再开根号避免权重值压得过于极端。3.3 train.py 训练流程与核心参数调整仓库里的train.py是入口脚本配置信息分离在config/training.yaml中通过environment.yml可以一键创建可复现的 conda 环境。重点参数集中在training.yaml的batch_size、base_lr、crop_size、epochs和backbone这几项。按照经验输入crop_size512时 batch size 在 8 到 16 之间可以占满一块 11GB 显存的显卡。学习率策略常用 poly 衰减即初始学习率乘以(1 - iter / total_iters) ** 0.9的幂指数在末尾阶段学习率降到接近 0让模型充分收敛。以下是我修改过的一个典型配置片段可以对照自己的显存调整# config/training.yaml 关键字段 model: backbone: mobilenet # 可选 mobilenet / shufflenet / darknet aspp_rates: [6, 12, 18] output_stride: 16 # 编码器下采样倍数8 更慢但边界更好 train: crop_size: 512 batch_size: 12 base_lr: 0.01 # 配合 poly 策略 power: 0.9 epochs: 120 warmup_epochs: 3 # 前几个 epoch 线性升 lr防止初期振荡output_stride指的是输入到最终特征图的空间下采样倍数。设为 16 时特征图是输入的 1/16对应空洞卷积的空洞率可以进行微调设为 8 时精度提升但显存用量接近翻倍。在足够显存的显卡上跑语义分割时如果目标是追求更高的 mIoUoutput_stride8的性价比很高如果目标是出 demo、跑视频流16 更现实。3.4 训练过程的监控指标iou_eval.py提供了逐类别的 IoU 计算训练中间产生的 checkpoint 通过utils/card.py生成可视化对比图。checkpoints/目录下有现成的 LiteSeg-mobilenet-card_epoch-99.pth 权重文件这个权重可以直接加载跑demo.py看效果。评估自己的模型时一定要同时看mIoU和class IoU的每个类别单独数值mIoU 被几个大类拉高的情况在长尾分布的数据集上极其常见只报一个总分会掩盖部分类别完全不可用的事实。4. 导出部署流程从 PyTorch 权重到端侧推理4.1 ONNX 导出的输入输出约定convert_to_onnx.py把训练好的权重转换为中间表示这是进入推理框架前的标准一步。PyTorch 模型需要锁定输入尺寸和 batch 维度后再导出因为 ONNX 文件里的 shape 信息对后续 TensorRT 或 OpenVINO 的优化起决定性作用。以下是导出时的常见参数设定python convert_to_onnx.py \ --checkpoint checkpoints/LiteSeg-mobilenet-card_epoch-99.pth \ --backbone mobilenet \ --height 512 \ --width 512 \ --opset 11opset版本决定转换器允许使用的算子集合。PyTorch 的torch.onnx.export里动态轴dynamic_axes虽然能导出可变分辨率模型但 TensorRT 会在运行期做形状推断动态 batch 的优化效果明显弱于固定 shape。在视频流应用里输入的分辨率通常是稳定的固定 h/w/batch 是更优做法前提是模型的crop_size尽量和部署尺寸一致避免训练与推理分辨率不一致导致的精度掉点。4.2 导出的输出 opset 版本与算子兼容转换完成后输出的 logits 形状是(1, num_classes, H, W)。常见推理框架的输入要求是固定的NCHW格式颜色顺序必须为 RGB。导出的过程如果遇到不支持的算子处理优先级是先换更高的opset版本PyTorch 官方对算子的支持力度随着 opset 新版本持续增强如果仍然失败把对应模块在模型代码里改写成算子友好的等价形式。4.3 TensorRT 加速与 INT8 量化转换得到 ONNX 后部署到英伟达的 GPU 平台可以用 TensorRT 做落地推理这也是资源包在 eval 环节最实用的加速手段。TensorRT 在 FP16 精度下通常可以获得 2 到 3 倍的加速比显存占用减小约一半。如果对精度损失敏感可以只对depthwise卷积层做 INT8 量化其余层保持 FP16。量化校准数据集一般取验证集里随机采样的 500 张图在校准时需要统计每个激活值的动态范围。INT8 推理对边界区域的输出影响最明显量化之后物体边缘往往会出现锯齿这是通道级量化无法避免的信息损失。# TensorRT Python API 推理核心流程 engine builder.build_engine(network, config) context engine.create_execution_context() context.set_binding_shape(0, (1, 3, 512, 512)) # 输入绑定 output np.empty((1, num_classes, 512, 512), dtypenp.float32) context.execute_v2([input_ptr, output_ptr])TensorRT 的构建阶段会做算子融合和层间内存复用但要注意模型里如果有torch.argmax这类非网络层操作不要直接放进导出范围后处理留在宿主代码里做。实际部署中 v2 版本直接运行好。5. 验证技巧与训练排错经验5.1 边界质量的量化验证语义分割评估如果只看 mIoU很容易忽略边界模糊的问题。mIoU 是通过区域重叠计算的指标对边界处 1 到 2 个像素的偏移不敏感但对视觉观感来说恰好是这些边缘位置的锯齿感和漏检最明显。此时可以用边界 F1 分数来补充验证把真实标签和模型预测都做 Canny 边缘提取再计算边缘像素的 Precision、Recall 和 F1。实现这个验证逻辑不复杂先对预测图做 one-hot 转换再逐类别计算边界图最后以几个像素宽度的容差做匹配。建议在评估脚本里单独加一组边界 IoU 统计观察它在不同主干下的变化趋势。以我验证 Mo-bileNet 主干和 DarkNet 主干的实践反馈前者的 mIoU 可能只掉两个点但边界 F1 会显著下降这个差距直接反映在人的视觉印象上。5.2 训练崩溃的常见原因排查训练在某个 epoch 突然出现 NaN 是常见问题。第一条定位路径是查看training.yaml里的base_lr很可能是学习率过大加上poly衰减末期的数值振荡造成的。可以把初始学习率降到原来的十分之一重新训练排查是否还出现 NaN。如果是权重初始化或者 BatchNorm 的 momentum 问题加载checkpoints/目录下的预训练权重继续训练能规避这类风险。显存不足时优先调整的是crop_size而不是batch_size。crop_size减半会带来 4 倍的显存节约同时面积减半意味着每个 batch 内的样本信息量变小可以同时把batch_size适当调大填平训练效率。在数据加载部分有参数量较大预算时可尝试激活torch.backends.cudnn.benchmark True加速卷积搜索。训练完成后做推理加速时优先把输入图像通过cv2.resize等比缩放到模型输入规格而不是直接裁剪保留宽高比能明显减少小目标丢失。最后在推理侧检查模型速度时确认 batch size 为 1、并且关了autograd梯度记录用torch.no_grad()包裹推理代码这是所有人最常忘记的一步开着梯度推理时显存占用高且计算会额外记录反向图实测耗时差距可能在 30% 以上。本文还有配套的精品资源点击获取
返回列表