语义分割这个方向,我从三年前开始断断续续地做,从最早的FCN到后来的DeepLab系列、SegFormer、Mask2Former,踩过的坑比跑通的模型多得多。很多人以为语义分割就是“把分类网络改成全卷积”这么简单,但真正上手之后你会发现,数据标注的坑、类别不平衡的坑、损失函数不收敛的坑、小目标分割崩掉的坑,一个比一个深。这篇内容我想把我在实际项目中积累的训练与优化经验完整地梳理一遍,不搞论文式的公式堆砌,就讲能直接用的东西。
无论你是刚接触语义分割的学生,还是已经跑过几个项目但效果总差那么一点的工程师,这篇内容应该都能帮你省下不少试错时间。我会从整体设计思路讲到具体的损失函数选型、数据增强策略、学习率调度、推理优化,再到实际排查问题的技巧,尽量做到每个环节都有可复现的操作方案。
1. 语义分割训练的整体设计思路拆解
1.1 为什么语义分割不能照搬分类网络的训练套路
很多人第一次做语义分割,习惯性地把分类任务的那套训练流程直接搬过来:交叉熵损失、固定学习率、简单翻转增强,然后发现训练loss降得挺好看,但mIoU就是上不去。问题出在哪儿?核心在于语义分割和分类任务的本质差异。
分类任务输出的是一个概率分布,关注的是“这张图里有什么”;语义分割输出的是逐像素的类别预测,关注的是“每个像素属于什么”。这意味着分割任务对空间信息的敏感度远高于分类。一个分类网络可能只需要关注全局特征就能判断出“这是一只猫”,但分割网络必须精确到每个像素的边界,这就要求网络在编码器和解码器之间建立高效的信息传递通道。
另一个关键差异是类别不平衡问题。在分类任务中,一张图只有一个或几个标签,类别不平衡通常通过采样策略就能缓解。但在分割任务中,一张640×640的图有四十多万个像素,如果背景占了95%,那前景类的梯度信号会被严重稀释。我试过在一个道路裂缝检测项目里直接用交叉熵损失,结果模型把所有像素都预测成背景,准确率高达97%,但mIoU只有不到2%。这就是典型的类别不平衡陷阱。
所以语义分割的训练设计,核心要解决三个问题:空间信息的保留与恢复、类别不平衡的梯度调节、边界区域的精细优化。后面讲的所有技巧,本质上都是在围绕这三个问题做文章。
1.2 编码器-解码器架构的选型逻辑
目前主流的语义分割架构可以分成三大类:FCN式的全卷积结构、编码器-解码器结构、以及基于Transformer的结构。选哪种,取决于你的具体场景和资源约束。
编码器-解码器结构(如U-Net、DeepLabV3+)是我最常用的方案。编码器负责逐层下采样提取语义特征,解码器负责逐层上采样恢复空间分辨率。中间的跳跃连接(skip connection)把编码器的高分辨率特征直接传到解码器,弥补下采样过程中丢失的空间细节。这个设计的直觉很好理解:编码器像是一个人在远处看风景,能看到整体但看不清细节;解码器像是走近了看,但需要编码器在远处看到的全局信息来辅助判断。
基于Transformer的结构(如SegFormer、Mask2Former)这两年很火,优势在于全局注意力机制能捕捉长距离依赖,对大场景的分割效果确实好。但代价是显存占用高、训练慢,而且在小数据集上容易过拟合。我的经验是:数据量在5000张以下、显存有限的情况下,优先选编码器-解码器结构;数据量充足(几万张以上)、显存够用,可以尝试Transformer方案。
还有一个容易被忽略的点是骨干网络的选择。ResNet系列是最稳妥的,预训练权重好找,社区支持完善。EfficientNet在同等精度下参数量更小,适合移动端部署。如果追求极致精度,Swin Transformer或ConvNeXt作为编码器效果更好,但训练成本也更高。我一般会先用ResNet-50跑一个baseline,确认数据和流程没问题之后,再换更强的骨干网络做对比实验。
1.3 训练流程的阶段性设计
语义分割的训练不适合一步到位,我习惯把它分成三个阶段:预热阶段、主训练阶段、微调阶段。
预热阶段通常跑5到10个epoch,学习率从很小的值线性增长到初始学习率。这个阶段的作用是让随机初始化的解码器慢慢适应编码器的特征分布,避免一开始就用大学习率把预训练权重带偏。我试过跳过预热直接上大学习率,结果编码器的预训练权重被破坏,模型收敛速度反而更慢。
主训练阶段是核心,用余弦退火或多项式衰减的学习率策略,配合数据增强和损失函数优化,让模型充分学习。这个阶段通常占整个训练过程的70%到80%的epoch数。
微调阶段用很小的学习率(通常是初始学习率的1/10到1/100)跑几个epoch,让模型在训练数据上做最后的精细调整。这个阶段可以关掉一些强增强(比如CutMix),让模型适应真实的数据分布。
注意:如果你的数据集很小(少于2000张),预热阶段可以缩短甚至跳过,但微调阶段一定要保留,否则模型很难收敛到好的局部最优。
2. 损失函数选型与类别不平衡处理的核心细节
2.1 交叉熵损失的局限性及改进方案
交叉熵损失是分割任务最基础的损失函数,但它对类别不平衡极其敏感。假设一张图里背景占90%,前景占10%,那么模型只要把所有像素都预测成背景,就能拿到90%的准确率,交叉熵损失也会降得很低。但这样的模型毫无用处。
解决这个问题的第一种思路是加权交叉熵。给每个类别分配一个权重,稀有类别的权重更高。权重的计算方式通常有两种:一种是取类别频率的倒数,另一种是取中位数频率除以各类别频率。我一般用后者,因为它不会让极稀有类别的权重过大导致训练不稳定。
第二种思路是Focal Loss。Focal Loss最初是为目标检测设计的,核心思想是让模型更关注难分类的样本。公式里有一个调节因子γ,当γ=0时退化为标准交叉熵,γ越大,对易分类样本的损失压制越强。在分割任务中,Focal Loss对边界像素和稀有类别的效果比较明显。但γ不能设太大,我一般取1到2之间,再大容易导致训练后期loss震荡。
第三种思路是Dice Loss。Dice Loss直接优化预测区域和真实区域的交并比,对类别不平衡天然不敏感。但Dice Loss的梯度在预测和真实区域完全不重叠时会变得很大,导致训练不稳定。所以实践中通常把Dice Loss和交叉熵损失按一定比例组合使用,比如0.5倍的Dice Loss加0.5倍的交叉熵损失。
2.2 组合损失函数的实战配置
在实际项目中,我很少只用单一损失函数,通常是组合使用。下面是我在多个项目中验证过的一套配置方案:
| 损失函数 | 权重 | 适用场景 | 注意事项 |
|---|---|---|---|
| 加权交叉熵 | 0.4 | 通用场景 | 权重取中位数频率的倒数 |
| Dice Loss | 0.4 | 类别极不平衡 | 需要平滑项防止除零 |
| Focal Loss | 0.2 | 边界精细分割 | γ取1.5左右 |
| 边界损失 | 0.1 | 边缘要求高 | 需要额外计算边界图 |
这套配置的核心逻辑是:加权交叉熵提供稳定的梯度信号,Dice Loss直接优化分割指标,Focal Loss关注难样本,边界损失专门优化边缘区域。权重比例可以根据具体任务调整,比如道路分割更关注连续性,可以加大Dice Loss的权重;医学图像分割更关注边界,可以加大边界损失的权重。
提示:组合损失函数的权重不要设得太极端,任何一项超过0.6都可能导致其他损失项被忽略。我一般让最大权重不超过0.5,保证各项损失都能贡献梯度。
2.3 类别不平衡的数据层面处理
损失函数是算法层面的解决方案,数据层面同样可以做文章。最直接的方法是过采样:在训练时提高包含稀有类别的图像的采样概率。具体做法是统计每张图中各类别的像素占比,如果某张图包含稀有类别,就给它更高的采样权重。
另一种方法是类别感知的数据增强。普通的随机裁剪可能会把稀有类别的区域裁掉,导致某些batch里完全没有稀有类别的样本。我通常会在裁剪时做一个判断:如果裁剪后的区域里稀有类别的像素占比低于某个阈值,就重新裁剪,直到满足条件或达到最大重试次数。
还有一种更精细的做法是像素级重采样。在计算损失时,对稀有类别的像素进行重复采样,让它们在损失计算中占更大的比重。这个方法和加权交叉熵有点类似,但更灵活,因为可以动态调整采样比例。
我在一个工业缺陷检测项目里试过这三种方法的组合:过采样让稀有类别的出现频率提高了3倍,类别感知裁剪保证了每个batch都有缺陷样本,像素级重采样进一步放大了缺陷区域的梯度。最终mIoU从0.42提升到了0.67,效果非常明显。
3. 数据增强策略与训练技巧的实操要点
3.1 空间增强与颜色增强的搭配原则
数据增强在分割任务中的重要性比分类任务更高,因为分割模型对空间位置和边界的敏感度更强。我把增强策略分成两类:空间增强和颜色增强。
空间增强包括随机翻转、随机旋转、随机缩放、随机裁剪。这些操作的共同点是会改变像素的空间位置,所以必须同时对图像和标注掩码做相同的变换。这一点听起来简单,但我见过不少人在实现时只变换了图像忘了变换掩码,导致训练时loss一直不降。翻转和旋转是最安全的增强方式,几乎不会引入噪声。缩放和裁剪需要注意尺度变化不能太大,否则小目标可能被缩得完全消失。
颜色增强包括亮度调整、对比度调整、色调调整、高斯噪声等。这些操作只改变像素值,不影响空间位置,所以只需要对图像做变换。颜色增强的作用是提高模型对光照和色彩变化的鲁棒性。但要注意,颜色增强不能太激进,否则可能改变类别的语义信息。比如在医学图像中,某些颜色变化可能对应不同的组织类型,过度增强反而会引入错误标签。
我的经验配置是:空间增强用翻转(概率0.5)、旋转(概率0.3,角度范围-15到15度)、缩放(概率0.3,尺度范围0.8到1.2);颜色增强用亮度调整(概率0.3,范围0.8到1.2)、对比度调整(概率0.3,范围0.8到1.2)。这套配置在大多数场景下都能稳定提升2到5个点的mIoU。
3.2 CutMix与Copy-Paste在分割任务中的特殊处理
CutMix和Copy-Paste是两种比较激进的增强方式,在分类任务中效果很好,但在分割任务中需要特殊处理。
CutMix的做法是从一张图中裁剪一个区域,粘贴到另一张图上。在分类任务中,标签按面积比例混合就行。但在分割任务中,粘贴区域的标签需要精确对应,而且粘贴边界处可能出现不自然的过渡。我的处理方式是:粘贴区域只从同类别的区域中裁剪,避免引入错误的类别标签;粘贴边界做一个高斯模糊过渡,减少边界伪影。
Copy-Paste的做法是从一张图中复制某个类别的实例,粘贴到另一张图的随机位置。这个方法对小目标分割特别有效,因为它可以人为增加小目标的出现频率。但要注意粘贴位置的合理性:不能粘贴到语义上不可能出现的位置(比如把天空中的飞机粘贴到地面上),也不能让粘贴的实例和原有实例严重重叠。我通常会用简单的规则做过滤:粘贴区域和已有同类实例的IoU不能超过0.3,粘贴位置不能跨越明显的语义边界。
这两种增强方式的效果比较依赖数据集的特点。在自然场景分割中,Copy-Paste对小目标(如行人、交通标志)的提升很明显;在医学图像分割中,CutMix对器官边界的鲁棒性有帮助。但如果数据集本身已经很大(几万张以上),这些增强的边际收益会下降,反而增加训练时间。
3.3 多尺度训练与测试的实操细节
多尺度训练是提升分割模型泛化能力的有效手段。具体做法是在训练时随机选择不同的输入分辨率,比如从0.5倍到1.5倍之间随机采样。这样模型能见到不同尺度的目标,对小目标和大目标的分割都更鲁棒。
但多尺度训练有一个坑:Batch Normalization的统计量会随尺度变化而波动。如果尺度变化范围太大,BN层的running mean和variance会不稳定,导致训练震荡。我的解决方案是:尺度范围控制在0.75到1.25之间,不要太大;如果用了预训练权重,前几个epoch先固定尺度,等BN统计量稳定后再开启多尺度。
多尺度测试(也叫Test Time Augmentation)是在推理时对同一张图用多个尺度分别预测,然后把结果融合。融合方式通常有两种:一种是取平均概率,另一种是取最大概率。我一般用平均概率,因为更稳定。多尺度测试通常能提升1到3个点的mIoU,但推理时间会成倍增加。如果对推理速度有要求,可以只用两个尺度(比如1.0和1.25)。
注意:多尺度测试的尺度选择要和训练时的尺度分布匹配。如果训练时只用了1.0倍尺度,测试时用0.5倍和1.5倍可能反而会降低效果。
4. 学习率调度与优化器配置的实战经验
4.1 学习率调度策略的对比与选择
学习率调度对分割模型的最终精度影响很大,我试过好几种策略,这里做一个对比。
Step Decay是最传统的策略,每隔固定的epoch数把学习率乘以一个衰减因子(通常是0.1)。优点是简单直观,缺点是衰减点需要手动调,而且衰减后学习率骤降可能导致模型陷入局部最优。
Cosine Annealing是余弦退火,学习率按余弦曲线从初始值平滑降到接近零。优点是全程平滑,没有突变,模型收敛更稳定。缺点是需要预设总epoch数,如果实际训练提前停止,学习率可能还没降到足够低。
Polynomial Decay是多项式衰减,学习率按多项式曲线下降。相比余弦退火,多项式衰减在训练后期的下降更平缓,适合训练epoch数较多的情况。我一般用power=0.9的多项式衰减。
One Cycle是一种先升后降的策略,学习率从很小的值线性升到最大值,再余弦降到接近零。这个策略在训练初期能快速探索参数空间,后期精细收敛。但最大学习率需要仔细调,太大容易发散,太小效果不明显。
我的选择逻辑是:如果训练epoch数在50以内,用One Cycle;50到200之间,用Cosine Annealing;200以上,用Polynomial Decay。初始学习率一般设0.01到0.02(SGD)或0.0001到0.0005(Adam)。
4.2 优化器选型与参数配置
分割任务的优化器选择主要看SGD和Adam两大阵营。
SGD加动量是经典配置,泛化性能通常更好,但收敛速度慢,对学习率敏感。我一般用SGD时设momentum=0.9,weight_decay=1e-4,学习率用上面说的调度策略。
Adam系列收敛快,对学习率不那么敏感,但泛化性能有时不如SGD。AdamW修正了Adam的权重衰减方式,在Transformer类模型中表现更好。我一般用AdamW时设weight_decay=0.01,betas=(0.9, 0.999)。
还有一个容易被忽略的参数是权重衰减。权重衰减的作用是防止过拟合,但在分割任务中,编码器的预训练权重通常不需要太强的权重衰减,解码器则需要。我的做法是分组设置权重衰减:编码器用1e-5,解码器用1e-4。这个细节看起来小,但在小数据集上能差出2到3个点的mIoU。
4.3 梯度累积与混合精度训练的配合
当显存不够跑大batch size时,梯度累积是一个实用的技巧。具体做法是:每次前向传播后不立即更新参数,而是累积梯度,累积N次后再统一更新。这样等效于把batch size放大了N倍。
梯度累积的注意事项是:Batch Normalization的统计量是按实际batch计算的,梯度累积不会改变BN的行为。所以如果显存只够跑batch size=2,梯度累积到16,BN的统计量仍然基于batch size=2,可能不够稳定。解决方案是改用Group Normalization或SyncBN。
混合精度训练(AMP)能显著减少显存占用和加速训练。PyTorch的AMP用起来很简单,几行代码就能开启。但要注意两点:一是损失缩放(loss scaling)需要动态调整,PyTorch的GradScaler会自动处理;二是某些操作在FP16下可能溢出,比如softmax在数值较大时。如果遇到NaN loss,先检查是不是FP16溢出导致的,可以尝试用FP32跑几个epoch再切换到AMP。
我在一个显存只有11GB的环境里,用梯度累积(累积8次)+ AMP,成功把等效batch size从4提升到了32,训练时间只增加了不到20%,最终mIoU比batch size=4时高了4个点。
5. 常见问题排查与避坑技巧实录
5.1 训练loss不收敛或震荡的排查思路
训练loss不收敛是新手最常遇到的问题,原因可能有很多,我按排查优先级列一下。
第一,检查数据标注是否正确。我遇到过好几次loss不收敛,最后发现是标注掩码的像素值不是从0开始的连续整数,而是有跳跃(比如0、1、2、255)。模型输出的类别数和标注的类别数对不上,自然不收敛。排查方法是打印标注掩码的唯一值,确认和类别数一致。
第二,检查学习率是否太大。学习率太大会导致loss震荡甚至发散。可以先把学习率降到原来的1/10跑几个epoch看看,如果loss开始下降,说明学习率确实太大了。
第三,检查损失函数是否有除零或log(0)的情况。交叉熵损失在预测概率为0时会出现log(0),导致inf。解决方案是给预测概率加一个很小的epsilon(如1e-7)。Dice Loss在预测和真实区域都为空时会出现除零,需要加平滑项。
第四,检查Batch Normalization的统计量是否稳定。如果batch size太小(比如2或4),BN的统计量波动很大,导致训练不稳定。解决方案是改用Group Normalization,或者用梯度累积增大等效batch size。
第五,检查是否有梯度爆炸。可以打印梯度范数,如果范数超过1000,说明梯度爆炸了。解决方案是加梯度裁剪(gradient clipping),把梯度范数限制在1到10之间。
5.2 mIoU上不去的典型原因与解决方案
loss降得很好但mIoU上不去,这是另一个常见问题。原因通常有以下几种。
过拟合是最常见的原因。训练集mIoU很高,验证集mIoU很低,说明模型记住了训练集的噪声。解决方案是增加数据增强、加权重衰减、用Dropout或DropBlock、早停(early stopping)。
类别不平衡是第二个常见原因。模型倾向于预测多数类,导致稀有类的IoU很低。解决方案就是前面讲的加权损失、Focal Loss、过采样等。
边界分割不准是第三个原因。整体mIoU还行,但边界区域的IoU很低。解决方案是加边界损失、用更精细的解码器(如OCR、PointRend)、提高输出分辨率。
小目标分割崩掉是第四个原因。小目标的IoU接近零。解决方案是Copy-Paste增强、多尺度训练、用高分辨率特征图做预测(如FPN结构)。
我整理了一个速查表,方便快速定位问题:
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| loss震荡 | 学习率太大 | 降学习率10倍 | 调小学习率或加预热 |
| loss不降 | 标注错误 | 打印标注唯一值 | 修正标注 |
| 验证mIoU低 | 过拟合 | 对比训练/验证曲线 | 增强、正则化、早停 |
| 稀有类IoU低 | 类别不平衡 | 统计类别像素占比 | 加权损失、过采样 |
| 边界IoU低 | 边界优化不足 | 可视化边界区域 | 边界损失、PointRend |
| 小目标IoU低 | 尺度问题 | 统计目标尺寸分布 | Copy-Paste、多尺度 |
5.3 推理速度优化的实用技巧
训练完之后,推理速度往往是部署时的瓶颈。我总结了几个实用的优化技巧。
模型剪枝是最直接的方法。把不重要的通道或层剪掉,参数量减少,推理速度提升。但剪枝后需要微调恢复精度,而且剪枝比例不能太大,否则精度掉得厉害。
量化是把FP32的权重和激活值转成INT8,推理速度能提升2到4倍。PyTorch支持动态量化和静态量化,静态量化需要校准数据,精度损失通常更小。我一般用静态量化,校准集用验证集的前100张图。
知识蒸馏是用大模型教小模型,让小模型达到接近大模型的精度。具体做法是用大模型的soft prediction作为小模型的目标,配合温度参数调节softmax的平滑程度。知识蒸馏对小模型部署特别有用,我试过用ResNet-101的模型教ResNet-18,小模型mIoU只掉了1.5个点,但推理速度快了3倍。
TensorRT是NVIDIA的推理加速库,能把模型编译成高度优化的推理引擎。支持FP16和INT8,加速效果很明显。但TensorRT对算子支持有限,某些自定义算子可能不支持,需要替换或重写。
提示:推理速度优化不要一步到位,先做量化和TensorRT,如果还不够再考虑剪枝和蒸馏。剪枝和蒸馏会改变模型结构,需要重新训练和验证,成本较高。
5.4 跨数据集泛化的注意事项
在一个数据集上训练好的模型,换到另一个数据集上效果往往大打折扣。这是域偏移(domain shift)问题,在分割任务中尤其明显。
解决域偏移的方法有几种。风格迁移是把目标数据集的风格迁移到源数据集上,让源数据看起来更像目标数据。对抗训练是加一个判别器,让模型提取的特征在不同域之间不可区分。自训练是先用源数据训练一个模型,在目标数据上生成伪标签,再用伪标签微调模型。
我的经验是:如果目标数据有少量标注,直接用目标数据微调效果最好;如果没有标注,自训练加风格迁移的组合比较实用。但要注意伪标签的质量,低质量的伪标签会引入噪声,反而降低效果。我一般会设置一个置信度阈值,只保留高置信度的伪标签。
6. 从训练到部署的完整实操流程
6.1 数据准备与标注质量检查
数据准备是分割项目的第一步,也是最容易被低估的一步。我见过太多项目因为标注质量问题导致模型效果上不去。
标注格式通常有几种:PNG掩码图、COCO JSON、VOC XML。PNG掩码图最直观,每个像素值对应类别ID。COCO JSON适合实例分割,语义分割也可以用。VOC XML是早期格式,现在用得少了。
拿到标注后,第一件事是可视化检查。把图像和掩码叠加显示,看看边界是否对齐、类别是否正确、有没有漏标或错标。我一般会随机抽100张图做检查,如果错误率超过5%,就需要重新标注或修正。
第二件事是统计类别分布。计算每个类别的像素占比,看看有没有极端不平衡的情况。如果某个类别的占比低于0.1%,需要考虑是否合并类别或过采样。
第三件事是检查图像和掩码的尺寸一致性。我遇到过图像是RGB三通道但掩码是单通道的情况,直接训练会报错。还有图像和掩码的尺寸不一致,需要resize对齐。
6.2 训练配置的完整参数表
下面是我在一个典型的分割项目中使用的完整配置,可以直接参考:
# 训练配置 config = { "model": "deeplabv3plus", "backbone": "resnet50", "num_classes": 8, "input_size": (512, 512), "batch_size": 8, "epochs": 120, "optimizer": "sgd", "lr": 0.01, "momentum": 0.9, "weight_decay": 1e-4, "lr_scheduler": "poly", "power": 0.9, "warmup_epochs": 5, "loss": { "ce": 0.4, "dice": 0.4, "focal": 0.2 }, "augmentation": { "flip": 0.5, "rotate": 0.3, "scale": 0.3, "brightness": 0.3, "contrast": 0.3 }, "amp": True, "grad_accum": 4 }这套配置在8GB显存的卡上能跑起来,等效batch size是32。如果显存更小,可以把batch_size降到4,grad_accum升到8。如果显存充足,batch_size可以直接设16或32,去掉grad_accum。
6.3 训练过程监控与日志分析
训练过程中的监控很重要,能帮你及时发现问题和调整策略。我一般会监控以下几个指标:
训练loss和验证loss:如果训练loss持续下降但验证loss开始上升,说明过拟合了,需要加正则化或早停。
学习率:确认学习率按预期调度,没有异常波动。
mIoU和各类IoU:整体mIoU反映模型性能,各类IoU帮你定位哪些类别效果差。
梯度范数:如果梯度范数突然增大,说明可能有梯度爆炸,需要加梯度裁剪。
显存占用:监控显存使用情况,避免OOM。
我习惯用TensorBoard或WandB记录这些指标,训练结束后对比不同配置的曲线,找出最优方案。还有一个实用技巧是保存每个epoch的验证集预测结果,训练结束后可视化对比,看看模型在哪些图上效果差,针对性地调整。
6.4 模型导出与推理部署
训练完成后,模型导出和部署是最后一步。PyTorch模型可以导出为ONNX格式,然后用ONNX Runtime或TensorRT推理。
导出ONNX时要注意几点:输入尺寸要固定,动态尺寸虽然支持但可能影响推理速度;算子要兼容,某些自定义算子可能不被ONNX支持;导出后要用ONNX Runtime验证输出是否和PyTorch一致。
推理部署时,预处理和后处理要和训练时保持一致。预处理包括归一化参数、resize方式;后处理包括argmax、resize回原图尺寸。我见过不少部署后效果变差的情况,最后发现是预处理不一致导致的。
如果部署在边缘设备上,还需要考虑模型大小和推理速度。可以用前面讲的量化、剪枝、蒸馏等技巧压缩模型。我一般会准备两个版本:一个高精度版本用于服务器部署,一个轻量版本用于边缘设备。
注意:模型导出后一定要做端到端的验证,用相同的输入对比PyTorch和部署引擎的输出,确保数值误差在可接受范围内(通常1e-3以内)。
7. 我个人在实际操作中的几点体会
做了这么多分割项目,有几个体会是反复被验证的。
第一,数据质量比模型结构重要。我试过用同一个模型在不同质量的数据集上训练,mIoU能差出20个点。花时间清洗数据、修正标注,比换更复杂的模型结构收益大得多。
第二,不要迷信SOTA。论文里的SOTA模型往往是在特定数据集上调过参的,直接搬到你的场景不一定好用。我一般先用简单的baseline跑通流程,再逐步尝试更复杂的方案,每次只改一个变量,这样才能清楚哪个改动真正有效。
第三,训练日志要详细记录。我习惯把每次实验的配置、曲线、最终指标都记录下来,过一段时间回头看,能发现很多当时没注意到的规律。比如我发现weight_decay从1e-4调到1e-5,mIoU稳定提升1个点左右,这个规律在多个项目里都成立。
第四,推理优化要提前考虑。如果项目最终要部署,训练时就要考虑模型大小和推理速度,不要等到训练完才发现模型太大跑不动。我一般会在训练中期就导出一个ONNX模型测一下推理速度,如果太慢就及时调整模型结构。
最后分享一个小技巧:如果验证集mIoU波动很大,可以试试用滑动平均(EMA)来平滑模型权重。EMA的做法是维护一份模型权重的移动平均,推理时用这份平均权重。这个方法几乎不增加训练成本,但能让mIoU稳定提升0.5到1个点,特别是在训练后期。