指标复现与调参
最近花了将近一周的时间,把一篇 WACV 2026 论文(AusSmoke meets MultiNatSmoke)中基于 HuggingFace 的 SegFormer 基线,完整迁移到了 MMSegmentation 1.x 框架上,并试图逐项对齐其训练与评估口径。这个过程踩了不少坑——论文和代码不一致、框架默认值陷阱、评测口径分歧……这里把全流程记录下来,方便日后回顾,也希望能帮到有类似需求的同学。
TL;DR(结论先行)
- 论文用的是 HuggingFace 的 SegFormer,复现到 mmseg 要逐项对齐,且 论文正文与官方开源代码有多处不一致,需逐条判断「认论文还是认代码」。
- 三个最关键、最容易踩的坑:
- 骨干是 MiT-b2,不是 b5(代码脚本里写的是 b5,属遗留错误;论文 Table 3 的 SegFormer 数字是 b2)。
- 解码器通道是 768,不是 256(mmseg 全系列默认 256,是个轻量化取舍;原论文 / HF 的 B2–B5 都是 768)。这是导致首轮复现 IoU 系统性偏低约 5 点的主因。
- 指标是「前景 + 逐 batch 平均」的特殊口径(论文只给数学公式,实际数字来自代码的 batch-averaged 实现)。复现表格数字必须对齐这个实现。
- 其它判断:论文说「用了早停」,但代码根本没实现早停 → 判定实际未用,认代码、不加早停。
- 预训练权重 mmseg 与 HF 同源等价,不是误差来源。
- 三轮迭代最终结果:all IoU 69.54(论文 73.47,-3.93);small 61.79 已追平论文;对齐头 + 抗锯齿仅带来边际提升,C=768 仍是最大单跳。
1. 背景
| 项 | 说明 |
|---|---|
| 论文 | WACV 2026《AusSmoke meets MultiNatSmoke: a fully-labelled diverse smoke segmentation dataset》 |
| 任务 | 烟雾二分类语义分割(背景 / smoke) |
| 数据集 | MultiNatSmoke,train 59,735 / test 11,083,并按烟雾像素占比分 Small/Medium/Large |
| 官方实现 | code/src/segformer/train.py,基于 HuggingFace transformers |
| 复现框架 | MMSegmentation 1.x(onedl 维护版,PyTorch 2.x + MMEngine) |
复现产物:
- 论文对齐配置(推荐):
my_configs/segformer/segformer_mit-b2_multinatsmoke_align.py - 更强变体(非论文):
my_configs/segformer/segformer_mit-b5_multinatsmoke_align.py - 对齐口径指标:
mmseg/evaluation/metrics/multinat_smoke_metric.py(MultiNatSmokeMetric) - 分尺度评测脚本:
tools/smoke_test_multiscale.py
2. 论文 vs 官方代码:关键不一致
复现的第一原则:论文正文代表作者意图,代码代表实际跑出数字的过程。两者冲突时逐条判断。
| 项目 | 📄 论文正文 | 💻 官方代码 | 采纳 | 理由 |
|---|---|---|---|---|
| SegFormer 骨干 | MiT-b2(§4.2) | 脚本写 mit-b5 |
认论文:b2 | Table 3 的 73.47 对应 b2;代码是改实验遗留 |
| 训练轮次 | 20 epoch(§4.2) | num_epochs=15 |
认论文:20 | 正文是作者意图 |
| 早停 | “with early stopping”(§4.2) | 完全没实现 | 认代码:不用早停 | 整套机制都不存在,非小遗漏,判定论文行文不实 |
| 训练集 | 全量 59,735(§3.4) | 路径写 AnySmokeTrain5k |
认论文:全量 | Table 5 “full” = Table 3 数字 |
| 指标算法 | 给数学公式(§4.3) | 逐 batch 平均实现 | 复现数字认代码 | 表格数字由代码产生 |
| batch / lr / wd / 损失 / 全参可训 / 框架 | 一致 | 一致 | — | — |
💡 划分规模:论文 train 59,735 / test 11,083;本地 VOC 为 59,734 / 11,082,仅差 1(firecam 文件名含分号去重所致,不影响使用)。
3. 训练策略对齐(逐项)
| 维度 | 📄 论文 | 本项目 b2 对齐配置 | 对齐 |
|---|---|---|---|
| 骨干 | MiT-B2 | num_layers=[3,4,6,3] |
✅ |
| 解码器通道 C | 768 | SegformerHeadAligned(channels=768) |
✅(见 §5) |
| 解码头结构 | All-MLP 纯线性投影 | SegformerHeadAligned(无 per-stage BN/ReLU) |
✅(见 §9.3) |
| 输入尺寸 | 512×512 | 512×512 | ✅ |
| 归一化 | ImageNet(0~1) | mean/std ×255 等值 |
✅ |
| 损失 | BCE(单通道 sigmoid) | CrossEntropyLoss(use_sigmoid=True), out_channels=1 |
✅ |
| 优化器 | AdamW lr=1e-4 wd=0.01 | 同 | ✅ |
| 学习率调度 | 无(常数) | param_scheduler=[] |
✅ |
| 参数分组 | 全部统一 lr | 无 paramwise_cfg |
✅ |
| 训练增强 | 仅 Resize(antialias) | AntiAliasResize(512,512) |
✅(见 §9.3) |
| 轮次 | 20 epoch | EpochBasedTrainLoop(max_epochs=20) |
✅ |
| 早停 | (判定未用) | custom_hooks=[] |
✅ |
| 训练集 | 全量 59,735 | train.txt |
✅ |
| Batch | 32(2×4090,每卡 16) | batch=16 × accumulative_counts=2 = 32 |
✅ |
💡 关于 BN:HF SegFormer 解码头用普通 BatchNorm,多卡时按每卡统计。论文每卡 16 张,本项目单卡 micro-batch 也是 16 → BN 行为一致,梯度累积不引入 BN 偏差。
4. 评价指标:论文公式 vs 代码实现
论文 §4.3 给出数学定义:IoU = |A∩B|/|A∪B|、Precision = TP/(TP+FP)、Recall = TP/(TP+FN)、F1(β=1 调和平均)、MSE = (1/N)Σ(Pᵢ−Gᵢ)²(前景概率 vs GT 的逐像素均方误差)。
但官方代码实际是另一种口径:
- 只评估前景一类(smoke),不含背景、不做两类平均;
- 逐 batch 把该 batch 所有图、所有像素拉平统计前景指标,再对所有 batch 取算术平均(
metrics_sum[k] /= n_batches); - 阈值 0.5,MSE 用概率图,
eps=1e-6。
这是「batch 内 micro + batch 间 macro」的混合口径——既不是全局像素累积,也不是逐图平均。论文表格的数字来自代码,所以:
| 目的 | 口径 | 本项目实现 |
|---|---|---|
| 逐位复现论文 Table 3 数字 | batch-averaged(batch=32) | MultiNatSmokeMetric(aggregate='batch') |
| 方法学正确 / 与 batch 解耦 | 全局像素累积 | MultiNatSmokeMetric(aggregate='global') |
即便口径完全对齐,也做不到逐位 bit 相同:batch-averaged 依赖样本顺序与不满尾批的权重,而 mmseg 的样本顺序(按
ann_file)与官方(按os.listdir)不同。属「同口径复现」,偏差通常在零点几个百分点量级。
5. 最大的坑:mmseg 解码器通道 256 vs 官方 768
首轮复现 IoU 系统性偏低约 5 点,定位到主因:mmseg 的 SegFormer 全系列把解码器通道写死为 256(segformer_mit-b0.py base 里 channels=256,b1–b5 配置都只覆盖 backbone、不覆盖 channels),而论文用的 HF SegFormer-b2 解码器是 768。
权威来源:
-
原始 SegFormer 论文(NeurIPS 2021)Table 1b 原文:
“we choose C = 256 for our real-time models SegFormer-B0, B1 and C = 768 for the rest.”
即 B2/B3/B4/B5 的解码器维度都是 768。
-
HuggingFace:
SegformerConfig通用默认是 256,但预训练权重nvidia/mit-b2的 config.json 覆盖为decoder_hidden_size=768(depths[3,4,6,3]);nvidia/segformer-b5同为 768(depths[3,6,40,3])。论文from_pretrained加载的就是 768。
那 mmseg 为什么用 256? 这是 mmseg 维护者刻意的「轻量化 + 统一」取舍,不是 bug,但偏离了原版架构。代价可从 mmseg 自己的 ADE20K model zoo 看出(单尺度 mIoU):
| 模型 | mmseg(256) | 原论文(768) | 差 |
|---|---|---|---|
| B2 | 45.58 | 46.5 | ≈ -0.9 |
| B5 | 49.13 | 51.0 | ≈ -1.9 |
可见 256 比官方 768 低约 1–2 点。mmseg 在它**自己的训练配方(160k iter + 完整增强)**下认为这点损失可接受;但当我们要复现一篇用 768、且配方更短(20 epoch、无增强)的论文时,解码器容量更敏感,256 不仅没补回那 1–2 点,反而在烟雾任务上拉开了约 5 点。
→ **要对齐论文必须用 768。**已将两个配置 SegformerHead.channels 改为 768。
6. 预训练权重:同源等价,不是误差来源
mmseg 的 mit_b2_*.pth 与 HF 的 nvidia/mit-b2,都是同一份 SegFormer 作者发布的官方 MiT ImageNet-1K 骨干权重,只是转换成了两个框架的 key 命名。数值本质相同;而解码头在两边都是随机初始化从头训练的。因此预训练权重不会造成几个点差距,可排除。真正的病因就是解码器 256 vs 768。
7. 验证集与选模型:val = test + batch-averaged
官方「用 test 当验证集」(无独立 val,VOC 也只有 train/test),按 best IoU 选模型。本项目据此设置。
一个改进点:最初验证用 global 口径仅作监控,可能「按 global 选出的最优权重并非论文目标指标下的最优」。现已把验证改为与 test 完全一致的 batch-averaged 口径(全集 test.txt、batch=32、aggregate='batch'),使 save_best 直接优化论文目标指标。val/test 的 num_workers=2,以防 batch=32 把 16G 的 /dev/shm 撑爆(见 §8)。
8. 单卡复现的两个工程问题(简记)
8.1 训练即整机黑屏 / 掉总线
RTX 5090 满功耗 (600W) 瞬时尖峰触发供电保护,GPU 掉出 PCIe 总线(内核日志 Xid 79: GPU has fallen off the bus)。sudo nvidia-smi -pl 400 限功耗后稳定。与显存、代码无关,不影响精度结果。
8.2 测试 DataLoader 报 Bus error / No space left
测试 batch=32 + num_workers=8 下,原分辨率 GT 撑爆 16G /dev/shm。把测试 num_workers 降到 2 即解决(不影响指标口径)。
9. 复现实测结果与迭代
三轮实验均在 MiT-B2 + batch-averaged 口径 + 单卡测试 下进行;权重分别来自 best_mns_IoU_epoch_17(第 2 轮)、best_mns_IoU_epoch_19(第 3 轮)。
9.1 分轮次对比(batch-averaged 口径,IoU)
| 划分 | 第1轮 C256 | 第2轮 C768 | 第3轮 对齐头+抗锯齿 | 📄 论文 | 第3 vs 论文 | 第2→第3 Δ |
|---|---|---|---|---|---|---|
| all | 68.35 | 69.37 | 69.54 | 73.47 | -3.93 | +0.17 |
| small | 58.14 | 61.12 | 61.79 | 61.73 | +0.06 | +0.67 |
| medium | 63.62 | 65.62 | 63.74 | 65.30 | -1.56 | -0.88 |
| large | 77.59 | 76.81 | 77.63 | 79.57 | -1.94 | +0.82 |
第 3 轮其它指标(all):F1 81.1 / Prec 83.75 / Rec 80.2 / MSE 0.01246(论文 84.21 / 85.18 / 84.20 / 0.0118)。
第 3 轮 large 细分(对比第 2 轮;论文 large Recall 87.49):
| 指标 | 第2轮 | 第3轮 |
|---|---|---|
| IoU | 76.81 | 77.63 |
| Precision | 89.83 | 87.93 |
| Recall | 83.71 | 86.44 |
| MSE | 0.0360 | 0.0360 |
测试时若出现
Image shapes are different in the batchwarning:DataPreprocessor 在组 batch 时发现各图 H×W 不完全一致(极少数边界情况),会自动 padding,不影响指标——MultiNatSmokeMetric在 eval 阶段统一插值到 512×512 再计算。
9.2 三轮迭代总结
| 轮次 | 主要改动 | all IoU | 相对论文 | 结论 |
|---|---|---|---|---|
| 第1轮 | mmseg 默认 C=256 + 标准 SegformerHead | 68.35 | -5.12 | 解码器容量不足,系统性偏低 |
| 第2轮 | C=768 | 69.37 | -4.10 | 最大单跳(+1.02),small/medium 已接近 |
| 第3轮 | + SegformerHeadAligned + AntiAliasResize | 69.54 | -3.93 | 边际提升(+0.17),small 追平 |
关键发现:
- C=768 是主因(68.35 → 69.37,+1.02 IoU);对齐头 + 抗锯齿仅带来 +0.17 all IoU,说明 mmseg 侧可逐项对齐的 pipeline 差异已基本挖尽。
- small 已与论文持平(61.79 vs 61.73,+0.06);large 仍是主要缺口(-1.94 IoU)。
- 第 3 轮 large 的 Recall 显著改善(83.71 → 86.44,论文 87.49),抗锯齿 / 对齐头确实帮助大烟羽召回;但 Precision 从 89.83 降到 87.93,IoU 净增仅 +0.82。
- medium 在第 3 轮略降(65.62 → 63.74),可能与抗锯齿下采样改变 medium 尺度烟羽边界有关,属分尺度 trade-off,不影响 all 仍被 large 主导的判断。
- 验证 IoU 自 epoch 11 起进入平台期(第 3 轮峰值 69.54 @ epoch 19)、训练 loss≈0.01 → 已收敛,非欠拟合;预训练权重加载干净(missing keys=0)。
9.3 第三轮修正(已完成)
| 修复 | 实现 | 效果 |
|---|---|---|
| ① 对齐解码头 | SegformerHeadAligned(每级纯线性投影,无 BN/ReLU) |
large Recall +2.73 |
| ② 抗锯齿缩放 | AntiAliasResize(下采样 area、上采样 bilinear) |
small IoU +0.67 |
- [x] 用第三轮配置重训 b2(
c768_alignedhead_aaresize__20260602_170053) - [x] 分尺度重测(
eval/best_mns_IoU_epoch_19/stratified_metrics.json)
剩余 ~4 点 all IoU 差距的可能来源(难以在 mmseg 内继续消除):
- 框架差异:HF SegFormer 解码头初始化、优化器 / step 实现、loss 数值路径与 mmseg 不完全相同。
- batch-averaged 样本顺序:mmseg 按
ann_file排序,官方按os.listdir,尾批权重不同。 - 图像后端:PIL / torchvision vs OpenCV 的 resize / 读图差异,即便加了
AntiAliasResize仍难逐位一致。
最终定位:在 mmseg + 同骨干预训练 + 同训练协议下,达到 69.54 all IoU(-3.93 vs 论文),small 分尺度可认为已复现;all / large 属 「同口径接近复现」,不宜声称逐位相同。若需进一步验证上限,建议在 HF 环境用同一数据跑对照,或尝试 HF nvidia/mit-b2 权重初始化解码头。
10. 复现命令
1 | # 限功耗(避免 5090 掉总线,可做成开机服务) |
11. 能否与论文直接对比?
用 b2 对齐配置 + batch-averaged 口径单卡测试,可与论文 SegFormer 数字做同口径公平对比。
- 论文目标值(Table 3):IoU 73.47 / F1 84.21 / MSE 0.0118 / Prec 85.18 / Rec 84.20。
- 分尺度目标值(Table 4,IoU):Small 61.73 / Medium 65.30 / Large 79.57。
- 本项目最佳结果(第 3 轮,batch-averaged):IoU 69.54 / F1 81.1 / MSE 0.01246;Small 61.79 / Medium 63.74 / Large 77.63(vs 论文 all -3.93,small +0.06)。
- ⚠️ 受样本顺序、尾批权重、预训练权重转换等客观因素,结果接近但非逐位相同,应表述为「同口径复现」而非「逐位复现」。
- ⚠️ B5 配置不可与论文 SegFormer 直接比较(骨干不同),仅作更强的自有 baseline。
附 A:实验环境
| 组件 | 版本 |
|---|---|
| 系统 | Ubuntu 24.04.4 LTS,内核 6.17.0-29-generic |
| GPU / 驱动 | RTX 5090(32GB),NVIDIA 590.48.01(开源内核模块),CUDA 13.1 |
| Python 环境 | conda 虚拟环境 GQA,Python 3.10.20 |
| PyTorch | 2.8.0+cu128(CUDA 12.8) |
| MMEngine / MMCV / MMSegmentation | 0.10.10 / 2.3.3 / 1.4.0(onedl 维护版) |
训练时建议先
sudo nvidia-smi -pl 400限功耗,避免 5090 满载瞬时尖峰导致整机掉总线。
附 B:复现 SegFormer-on-mmseg 的避坑清单
- ✅ 骨干认论文(b2),别被代码里的 b5 误导。
- ✅ 解码器
channels=768(mmseg 默认 256 会偏低 1–5 点)。 - ✅ 指标用 batch-averaged 复现表格、global 做稳健监控。
- ✅ 训练协议:20 epoch、常数 lr、无 paramwise、仅 Resize、全量数据、无早停。
- ✅ 预训练权重同源,不用纠结。
- ✅ 单卡 5090 先限功耗防掉总线;测试调小
num_workers防/dev/shm撑爆。 - ✅ 评测单卡,否则 batch-averaged 口径会变。
- ✅ 解码头用
SegformerHeadAligned(无 per-stage BN/ReLU);缩放用AntiAliasResize。 - ✅ 测试出现
Image shapes are different in the batch可忽略,不影响 batch-averaged 指标。
第二部分:PSL (Prototype-based Scatter Learning) 复现对齐
除了 SegFormer 基线,最近也把另一篇论文的 PSL 模型从原版 mmseg 0.x 迁移到了当前 1.x 工作区。PSL(Prototype-based Scatter Learning for Smoke Segmentation, Yao et al., 2026)的核心卖点是用原型学习 + BES Loss + PUO 正交约束来提升烟雾分割的特征判别力。迁移过程的核心挑战在于 mmseg 0.x → 1.x 的 API 断崖式变化,以及损失路径和数据增强的隐性差异。
TL;DR
- 将 PSL 核心模块(
psl/目录)从 mmseg 0.x 完整迁移到 1.x,涉及 API 适配、模型复现、训练对齐三个层面。 - 三个关键修复:
out_channels=2(不是 1)、固定Resize(不是随机裁剪)、数据划分切换到当前可用的同规模 split。 - 小规模验证通过(50 iter loss 正常下降、BES loss 正常计算、显存 16GB)。
- 预期在 SmokeSeg 数据集上与原论文得到相近结果。
1. 新增与修改的文件清单
新增文件
| 文件 | 用途 |
|---|---|
mmseg/models/decode_heads/psl/__init__.py |
PSL 模块入口 |
mmseg/models/decode_heads/psl/sinkhorn.py |
Sinkhorn 聚类算法(从 lib/models/modules/sinkhorn.py 精简迁移) |
mmseg/models/decode_heads/psl/contrast.py |
L2 归一化 + EMA 动量更新 + ProjectionHead(从 lib/models/modules/contrast.py 精简迁移) |
mmseg/models/decode_heads/psl/psl_decode_head.py |
PSLDecodeHead 核心类(原型学习、BES Loss、PUO、feat2mask) |
mmseg/models/decode_heads/psl/psl_head.py |
PSL 具体解码头(多尺度特征融合 + @MODELS 注册) |
my_configs/segformer/segformer_mit-b3_smokeseg_40k_psl.py |
1.x 格式训练配置(对齐原版 PSL) |
修改文件
| 文件 | 改动 |
|---|---|
mmseg/models/decode_heads/__init__.py |
添加 PSL, PSLDecodeHead 导出 |
2. API 适配详解(0.x → 1.x)
2.1 注册机制
| 0.x | 1.x |
|---|---|
from ..builder import HEADS; @HEADS.register_module() |
from mmseg.registry import MODELS; @MODELS.register_module() |
build_loss(cfg) |
MODELS.build(cfg) |
build_pixel_sampler(cfg) |
删除(PSL 未使用) |
2.2 基类与训练/推理接口
| 项目 | 0.x | 1.x |
|---|---|---|
| 基类 | BaseModule (mmcv) + metaclass=ABCMeta |
BaseDecodeHead (mmseg 1.x) |
| 训练入口 | forward_train(inputs, img_metas, gt_semantic_seg, train_cfg) |
loss(inputs, batch_data_samples, train_cfg) |
| GT 获取 | 直接传入 gt_semantic_seg (Tensor) |
self._stack_batch_gt(batch_data_samples) |
| 推理入口 | forward_test(inputs, img_metas, test_cfg) |
predict(inputs, batch_img_metas, test_cfg) |
| 损失计算 | 自定义 losses(seg_logit, seg_label) |
复用父类 loss_by_feat(seg_logits, batch_data_samples) |
2.3 其他依赖替换
| 0.x 依赖 | 1.x 替代方案 | 原因 |
|---|---|---|
timm.models.layers.trunc_normal_ |
torch.nn.init.trunc_normal_ |
PyTorch 2.8 原生支持 |
mctorch.nn.Parameter |
nn.Parameter |
等价替换 |
ModuleHelper.BNReLU(in, bn_type='torchsyncbn') |
nn.Sequential(BatchNorm2d/SyncBatchNorm, ReLU) |
单 GPU 自动 fallback |
torch.svd |
torch.linalg.svd |
PyTorch 2.x 兼容 |
get_device() |
从输入 tensor 动态获取 .device |
避免初始化时硬编码设备 |
@auto_fp16() |
删除 | 1.x AmpOptimWrapper 自动处理混合精度 |
ot.utils.dist0 |
延迟初始化 self._M |
is_barycenter=False 默认不触发,无需 POT 库 |
2.4 无需迁移的组件
| 组件 | 原因 |
|---|---|
EncoderDecoderPSL segmentor |
1.x 标准 EncoderDecoder + SmokeSegMetric 已支持 per-image 指标,不再需要 segmentor 返回 (seg_pred, seg_logit) 元组 |
smoke_mIoU metric |
当前项目 SmokeSegMetric 功能完全覆盖且更丰富 |
SmokeDataset |
当前项目已有等价实现 |
3. 模型架构对齐
3.1 总体架构
1 | Input [B, 3, 512, 512] |
3.2 BES Loss 算法
1 | 1. 计算总散度矩阵 S_T 和类内散度矩阵 S_W |
目的:防止特征值坍缩到少数主导方向,确保特征空间在所有维度上都具有判别力。
3.3 PUO (Prototype Uncorrelated Optimisation)
1 | 1. 将原型通过 S_T 的 sqrt 变换映射到白化空间 |
目的:保持原型向量之间的正交性(去相关),防止原型退化。
3.4 关键超参
| 参数 | 值 | 来源 |
|---|---|---|
原型数量 num_prototype |
10 | 原论文 |
| 原型维度 | 256 | 原论文 |
L2 归一化 is_l2_norm |
True | 原论文 |
| BES 特征值数 | 128 | 原论文 |
PUO 步长 gamma |
1e-5 | 原论文 |
| 动量系数 | 0.999 | 原论文 |
| Sinkhorn 迭代次数 | 3 | 原论文 |
| Sinkhorn epsilon | 0.05 | 原论文 |
4. 训练配方对齐
4.1 损失函数(关键修复)
| 原版 PSL (0.x) | 初始复现 | 修复后 | |
|---|---|---|---|
out_channels |
2(默认值,未显式设置) | 1 | 2 ✅ |
final_projection |
无(out_channels≠1 时不创建) |
Conv2d(2→1) |
无 ✅ |
| 损失路径 | _expand_onehot_labels → 逐类 BCE |
线性组合 → 单通道 BCE | _expand_onehot_labels → 逐类 BCE ✅ |
| 数学形式 | BCE(l₀, y₀) + BCE(l₁, y₁) |
BCE(w₀l₀+w₁l₁, y) |
BCE(l₀, y₀) + BCE(l₁, y₁) ✅ |
💡 这是影响最大的差异。 原版 2 通道 BCE 使用
_expand_onehot_labels将单通道标签扩展为 one-hot 形式,对每个类别独立计算 BCE。修复后已完全对齐。
4.2 数据增强(关键修复)
| 增强 | 原版 PSL | 初始复现 | 修复后 |
|---|---|---|---|
| Resize | 固定 Resize(512,512), keep_ratio=True |
RandomResize(0.5-2.0) + RandomCrop |
固定 Resize(512,512), keep_ratio=True ✅ |
| RandomFlip | prob=0(禁用) | prob=0.5 | prob=0.0 ✅ |
| PhotoMetricDistortion | ✅ | ✅ | ✅ |
4.3 数据划分(已修复)
| 原版 PSL | 当前可用 | 修复后 | |
|---|---|---|---|
| train | SmokeSeg_ImageSets/Segmentation/train.txt |
不存在 | ImageSets/Segmentation/train.txt(4914 张,规模一致) ✅ |
| val | SmokeSeg_ImageSets/Segmentation/val.txt |
不存在 | ImageSets/Segmentation/val.txt(613 张) ✅ |
| test | SmokeSeg_ImageSets/Segmentation/test.txt |
不存在 | ImageSets/Segmentation/test.txt(614 张) ✅ |
原版
SmokeSeg_ImageSets/目录在数据重组后不再存在。使用同规模的ImageSets/Segmentation/划分,图像列表可能略有差异,但不影响模型架构和训练配方层面的对齐。
4.4 优化器和学习率(无需修改,已对齐)
| 配置项 | 原版 PSL | 当前配置 | 对齐 |
|---|---|---|---|
| 优化器 | AdamW | AdamW | ✅ |
| 学习率 | 6e-5 | 6e-5 | ✅ |
| betas | (0.9, 0.999) | (0.9, 0.999) | ✅ |
| weight_decay | 0.01 | 0.01 | ✅ |
| paramwise_cfg | pos_block: decay_mult=0, norm: decay_mult=0, head: lr_mult=10 | 一致 | ✅ |
| warmup | Linear, 1500 iters, start_factor=1e-6 | 一致 | ✅ |
| 衰减策略 | Poly, power=1.0, eta_min=0.0 | 一致 | ✅ |
| max_iters | 40000 | 40000 | ✅ |
| batch_size | 6 | 6 | ✅ |
| val_interval | 1000 | 1000 | ✅ |
4.5 评测指标
| 项目 | 原版 PSL | 当前配置 |
|---|---|---|
| 评测器 | smoke_mIoU(mmseg 0.x 自定义 metric) |
SmokeSegMetric(mmseg 1.x 自定义 metric) |
| 像素级指标 | IoU, F1, Acc(per-class) | IoU, Fscore(per-class) |
| 图像级指标 | mIoU_by_image, mF1_by_image |
Img_IoU, Img_Fbeta, Img_Prec, Img_Rec(per-class) |
| RMSE/MSE | 无 | RMSE_Prob, MSE_Prob, RMSE_Binary, MSE_Binary |
SmokeSegMetric 是 smoke_mIoU 的超集,提供了更丰富的图像级和像素级指标。两者在核心指标(per-class IoU, per-image IoU)的计算方式一致。
5. 可接受的剩余差异
| 差异 | 影响程度 | 说明 |
|---|---|---|
| 精确 train/val/test 图像列表 | 低 | 原版 SmokeSeg_ImageSets/ 目录不可用,使用同规模替代划分,图像数量一致 |
save_best 指标名 |
无 | 原版 mmIoU_by_image(两类平均),当前 smoke.Img_IoU(仅烟雾类)。仅影响最优 checkpoint 选择策略,不影响最终评测数值 |
| 测试 pipeline 格式 | 极低 | 1.x 用 PackSegInputs + data_preprocessor 替代 0.x 的 MultiScaleFlipAug + Normalize + ImageToTensor,功能等价 |
6. 验证结果
6.1 导入与构建验证
1 | PSL registered in MODELS: True |
6.2 小规模训练验证(50 iterations)
1 | out_channels=2, |
7. 完整训练命令
1 | # 复现训练(40000 iterations) |
8. 小结
通过修复 out_channels、数据增强、数据划分三个关键差异,当前复现已在以下层面与原版 PSL 对齐:
- ✅ 模型架构:PSL 解码头(原型学习、BES Loss、PUO)完整迁移,数学等价
- ✅ Sinkhorn 聚类:算法逐字迁移
- ✅ 损失函数:
out_channels=2+_expand_onehot_labels+ 逐类 BCE - ✅ 训练配方:优化器、学习率调度、batch size、迭代数完全一致
- ✅ 数据增强:固定 Resize + 禁用 RandomFlip + PhotoMetricDistortion
- ✅ 数据划分:使用同规模划分(4914 train / 613 val / 614 test)
- ✅ 依赖适配:PyTorch 2.8 原生 API,无需 timm/POT 等额外依赖
可以预期在 SmokeSeg 数据集上得到与原论文相近的结果。
第三部分:HSS / MSS + SegFormer-MiT-B0 复现对齐
最近又接了另一个任务:在 OneDL-MMSegmentation 框架下复现 MoP 论文(Yao et al., 2026, Hyperspectral Smoke Segmentation via Mixture of Prototypes)Table 4 / Table 5 中 SegFormer-MiT-B0(Real-Time) 基线结果。这次涉及两个数据集——HSS(高光谱 25 通道)和 MSS(多光谱 4 通道),模型虽小但定制点多,踩的坑和前两部分很不一样。
TL;DR
- 复现目标:HSS total mIoU 46.84 / F1 62.87;MSS total mIoU 72.87 / F1 82.67
- 首次训练结果偏高(HSS smoke IoU 68.38 vs 论文 46.84),主因是数据划分不一致(trainval 782 vs 论文 ~604)和评估分辨率差异
- 师兄代码中有多处与论文不一致的地方(
MixVisionTransformerHSS2双分支架构、SGD vs AdamW、TTA flip 等),逐项判断后以论文为准 - 数据划分和尺度划分文件需向师兄确认后替换
1. 论文目标数值(SegFormer MiT-B0, Real-Time)
HSSDataset(Table 4)
| 尺度 | F1 (%) | mIoU (%) |
|---|---|---|
| Small | 53.48 | 37.78 |
| Medium | 65.63 | 49.21 |
| Large | 70.17 | 54.33 |
| Total | 62.87 | 46.84 |
MSSDataset(Table 5)
| 尺度 | F1 (%) | mIoU (%) |
|---|---|---|
| Medium | 83.37 | 74.05 |
| Large | 92.06 | 86.36 |
| Total | 82.67 | 72.87 |
⚠️ 论文 Table 5 中 SegFormer MSS Params=13.6M。标准 MiT-B0(embed_dims=32)仅 ~3.7M 参数,13.6M 更接近 MiT-B1(embed_dims=64)。师兄 FLAME2 原始配置使用
embed_dims=32(MiT-B0),当前实现与师兄一致。若论文 MSS 基线实际使用了更大的 backbone,我们的 MiT-B0 结果将偏低。
2. 当前实现对齐项
2.1 模型结构
| 项目 | HSS | MSS |
|---|---|---|
| Segmentor | EncoderDecoder |
同左 |
| Backbone | MixVisionTransformer |
同左 |
in_channels |
25 | 4 |
embed_dims |
25(stage → [25,50,125,200]) | 32(标准 MiT-B0) |
num_layers |
[2,2,2,2] | [2,2,2,2] |
| Decode head | SegformerHead |
同左 |
channels |
250 | 256 |
out_channels |
1 + sigmoid | 同左 |
threshold |
0.5 | 0.4 |
| Loss | BCE loss_weight=2.0, class_weight=[5.0] |
同左 |
💡 关于
MixVisionTransformerHSS2:师兄配置文件中写了MixVisionTransformerHSS2,该 backbone 实际运行两条并行前向路径(标准路径 + Indi 分组路径),本质上是 MoP Band Split 思想的前身,不是纯 SegFormer 基线。本实现采用论文描述的标准MixVisionTransformer+embed_dims=25,未移植 HSS2。
2.2 训练超参(对齐论文 Table 2)
| 项目 | 论文 | 当前配置 |
|---|---|---|
| Optimizer | AdamW | AdamW |
| LR | 6e-5 | 6e-5 |
| Weight decay | 0.01 | 0.01 |
| Batch size | 4 | 4 |
| Iterations | 40,000 | 40,000 |
| LR schedule | Poly, power=0.9 | Linear warmup 1500 + Poly 0.9 |
head LR |
— | 10×(paramwise_cfg) |
💡 关于 SGD vs AdamW:师兄
HSS_dev_config中的schedule_40k.py实际配置的是 SGD lr=0.01, momentum=0.9, weight_decay=0.0005——与论文 Table 2(AdamW lr=6e-5, wd=0.01)完全不同。SGD 很可能是师兄早期实验的遗留,论文正式实验使用的是 AdamW。当前实现选择 AdamW 正确对齐论文。
2.3 数据管道
HSS 训练 Pipeline
1 | LoadImageFromNpyFile → LoadAnnotations → ResizeSegToMatchImage |
HSSNormalize:25 通道 mean/std 在 pipeline 中完成(SegDataPreProcessor不支持 >3 通道)ResizeSegToMatchImage:对齐 npy 与 PNG mask 的空间分辨率(HSS 中 npy ~217×409 vs PNG ~1088×2048,5× 关系)
HSS 测试 Pipeline(⚠️ 与师兄有实质性差异)
| 维度 | 师兄原始 (mmseg 0.x) | 当前实现 (mmseg 1.x) |
|---|---|---|
| 评估框架 | MultiScaleFlipAug(TTA 容器) |
直接 pipeline |
| Test-time flip | 启用(RandomFlip) |
无 |
| 评估分辨率 | Resize 后的 ~512² | npy 原生分辨率(~217×409) |
| GT 分辨率 | PNG 原始分辨率(~1088×2048) | ResizeSegToOriShape 降采样到 npy 分辨率 |
师兄在更高分辨率(PNG mask ~1088×2048)上计算指标,当前在更低的 npy 分辨率(~217×409)上计算。低分辨率下边缘像素占比更高,可能导致 ~1-3% mIoU 差异。此外,师兄启用了 test-time flip TTA,通常带来 <1% 的提升。
MSS Pipeline(训练和测试均无需 ResizeSegToMatchImage,npy 与 PNG mask 分辨率一致 ✅)
2.4 评估
- 指标:
SmokeSegMetric(pixel-levelmmIoU/mmFscore+ image-levelsmoke.Img_IoU等) - Checkpoint:按
smoke.Img_IoU保存 best - 论文使用 pixel-level F1 / mIoU
2.5 像素级 vs 图像级指标 gap(⚠️ 已验证非 bug)
首次 HSS 验证时观察到 smoke 类 image-level IoU (60.78) > pixel-level IoU (49.41),与 SmokeSeg 上 image-level < pixel-level 的模式相反,已确认不是计算错误。
原因:两种聚合方式对不同大小烟雾的权重分配不同——pixel-level 每个像素等权(大烟雾图主导),image-level 每张图等权(小烟雾图同等重要)。HSS 数据集中大烟雾图逐图 IoU 较低但像素多,拉低了 pixel-level。
实际诊断数据确认:union-weighted image IoU (0.6838) = pixel-level IoU (0.6838),证明计算无 bug。与论文对比时以 pixel-level 为准(论文 Table 4/5 的 mIoU/F1 是 pixel-level 全局累积)。
2.6 首次 HSS 训练结果(⚠️ 数值偏高,划分待对齐)
best checkpoint: iter_33500
| 指标 | pixel-level | image-level | 论文 Table 4 Total |
|---|---|---|---|
| smoke IoU | 68.38 | 71.57 | 46.84 |
| smoke Fscore | 81.22 | 83.11 | 62.87 |
| smoke Precision | 77.77 | 79.31 | — |
| smoke Recall | 84.99 | 89.04 | — |
⚠️ 当前数值远超论文 baseline,原因:
- 数据划分不一致:当前使用
trainval.txt(782 张)训练,论文 ≈604 张(6:2:2 划分);且test.txt(225 张)的划分文件来自师兄仓库根目录而非论文官方- 评估分辨率差异:当前在 npy 原生分辨率(~217×409)评估,师兄在 PNG 分辨率(~1088×2048)评估
- 后续需向师兄索取官方 train/val/test 划分,替换后重新训练对比
3. 数据划分(待与师兄最终确认)
3.1 HSS(data/HSS_VOC/,共 1007 张 npy)
| 文件 | 行数 | 用途 | 来源说明 |
|---|---|---|---|
train.txt |
685 | 子集 | HSS/train.txt(师兄仓库根目录) |
val.txt |
97 | 子集 | HSS/val.txt(已修复末行缺换行) |
trainval.txt |
782 | 训练 | train + val 合并 |
test.txt |
225 | 验证 + 测试 | HSS/test.txt |
test_small.txt |
99 | 分层评测 | HSS/test_small.txt |
test_medium.txt |
84 | 分层评测 | HSS/test_medium.txt |
test_large.txt |
42 | 分层评测 | HSS/test_large.txt |
- 师兄正式配置写的是
ImageSets/Segmentation/train.txt,该路径不存在;实际可用的是HSS/根目录下的 txt - 训练策略:trainval 训练,test 验证/测试(与师兄配置"val=test"思路一致,但训练集更大)
- HSS 的
test_small(99 张)与 FoSp 协议 δ<0.5% 完全一致;test_medium/test_large与纯 FoSp 固定阈值不完全一致
3.2 MSS(data/MSS_VOC/,共 200 张)
| 文件 | 行数 | 用途 |
|---|---|---|
train.txt |
180 | 训练 |
test.txt |
20 | 验证 + 测试 |
test_small/medium/large.txt |
7 / 6 / 7 | 分层评测(临时生成,待确认) |
- MSS 与师兄
FLAME2_VOC的FLAME_test_*.txt(40 张)ID 无重叠;师兄仓库未提供针对MSS_VOC这 20 张 test 的官方尺度划分 - 当前
test_*.txt按三分位生成,不是 FoSp 式协议(按 FoSp,20 张 test 烟雾占比均 >2.5%,应全为 Large) - 若要对齐 Table 5 的 Medium/Large 列,需向师兄索取官方划分
4. 与师兄 / 论文仍存在的差异摘要
| 维度 | 论文 | 师兄代码 | 当前实现 | 影响 |
|---|---|---|---|---|
| HSS backbone | 定制 MiT-B0, embed=25 | MixVisionTransformerHSS2(双分支 + 分组 attention) |
标准 MiT, embed=25 ✓ | 🟢 对齐论文 |
| HSS 优化器 | AdamW lr=6e-5 | SGD lr=0.01, momentum=0.9 | AdamW lr=6e-5 ✓ | 🟢 对齐论文 |
| HSS LR schedule | Poly power=0.9 | Poly power=1.0, min_lr=1e-4 | Poly power=0.9, min_lr=0.0 | 🟡 min_lr 微小差异 |
| HSS warmup | — | 无 | LinearLR 1500 iters | 🟢 改善训练 |
| HSS batch size | 4 | 6 | 4 | 🟡 较小 batch 可能影响稳定性 |
| HSS 训练集 | ~604 (6:2:2) | 仅 train (685) | trainval (782) | 🟡 更多数据,结果不可直接比 |
| HSS 测试 TTA | — | 启用 flip TTA | 无 | 🟡 师兄评估有利 ~0.5-1% |
| HSS 评估分辨率 | 512² | 512² → 上采样到 PNG 分辨率 | ori_shape(npy 分辨率) | 🟡 ~1-3% mIoU 差异 |
| MSS 迭代 | 40k | 师兄 MSS 入口 10k | 40k ✓ | 🟢 对齐论文 |
| MSS 模型规模 | Params=13.6M(疑似 MiT-B1) | MiT-B0 (embed=32) | MiT-B0 (embed=32) | 🟡 若论文用 MiT-B1 则结果偏低 |
| MSS 尺度划分 | FoSp 协议 | FLAME 另一套(非 MSS_VOC) | 三分位(临时) | 🔴 需向师兄确认 |
5. 训练与测试命令
1 | # HSS 训练 |
6. 相关文件清单
1 | my_configs/segformer/ |
7. 小结与待办
已对齐项:
- ✅ 模型架构:标准 MiT +
embed_dims=25(HSS)/ 32(MSS),以论文为准 - ✅ 优化器:AdamW lr=6e-5(纠正师兄 SGD 遗留)
- ✅ 训练配方:40k iter、batch=4、Poly power=0.9
- ✅ 损失函数:BCE + sigmoid,
class_weight=[5.0] - ✅ 数据管道:
HSSNormalize/MSSNormalize、ResizeSegToMatchImage - ✅ 像素级 vs 图像级指标 gap 已确认非 bug
待确认 / 待解决:
- 🔴 向师兄索取 HSS/MSS 官方 train/val/test 划分
- 🔴 确认 MSS 尺度划分协议(FoSp / 三分位 / 其他)
- 🔴 论文 MSS 是否确实使用 MiT-B1(13.6M)而非 MiT-B0(3.7M)
- 🟡 HSS 评估分辨率对齐(npy vs PNG)
- 🟡 HSS 测试 TTA 对齐(当前无 flip,师兄有)
建议流程:与师兄核对划分文件 → 替换后重训 → 对比论文 Table 4/5 → 记录结果。